+14

Tăng tốc React app của bạn với dynamic imports và code splitting

Tối ưu hóa hiệu suất là một phần phát triển quan trọng mà mọi lập trình viên phải đối mặt

Chúng ta không thể có một lượng người dùng lớn nếu như trang web của chúng ta xử lí các tác vụ một cách chậm chạp, hoặc là khi cố gắng điều hướng sang một trang khác trong ứng dụng, người dùng ngay lập tức nhận thấy thời gian tải cao.

Tại thời điểm đó, chúng ta biết là cần phải bắt tay ngay vào việc tối ưu hóa hiệu suất.

Trong khi phát triển trên trên localhost, chúng ta hầu như không gặp phải bất kỳ vấn đề nào về hiệu suất, nhưng đó là do có sự khác biệt giữa môi trường productiondevelopment

Trong khi phát triển trên locahost, tất cả các files của chúng ta đều được lưu trữ trong máy tính.

Trong React, cổng được mặc định là 3000. Vì kết nối internet không thành vấn đề trong khi đang sử dụng máy chủ cục bộ, chúng ta có thể tải xuống tất cả các files và các gói javaScript của mình một cách cực kỳ nhanh chóng.

Tuy nhiên, việc tải xuống các tệp lớn và các gói JavaScript sẽ trở thành một vấn đề lớn khi chúng ta thực hiện trên môi trường production, đặc biệt là ở những nơi có thể không có Internet tốc độ cao.

Có một số kỹ thuật và thủ thuật tối ưu hóa hiệu suất để sử dụng với React. Trong bài viết này, chúng ta sẽ xem xét cách cải thiện hiệu suất bằng cách sử dụng code splitting

The benefits of code splitting

Một lợi ích tuyệt vời khi sử dụng ứng dụng create-react-app là nó cung cấp cho chúng ta khả năng code splittingchunking.

Chunking cho phép chúng ta tách các đoạn code của mình thành các gói nhỏ hơn, là một nhóm các phần liên quan được đóng gói thành một tệp duy nhất.

Các công cụ như create-react-app, Gatsby.jsNext.js đều sử dụng webpack để đóng gói các ứng dụng.

Các gói như webpack import tất cả các files ứng dụng và merge chúng thành một gói duy nhất.

Một số lợi ích của việc này là:

  • Cho phép trình duyệt của người dùng tải xuống toàn bộ ứng dụng một lần để họ có thể điều hướng liền mạch mà không cần các request HTTP khác.

  • Trình duyệt không cần yêu cầu hoặc import bất kỳ tệp nào khác vì tất cả chúng đều nằm trong gói đã tải xuống

  • Nói thêm về React:

React sử dụng cơ chế Client Side Rendering(CSR) sẽ tải hết các file tĩnh HTML, js của ứng dụng về phía client để sử dụng.

Vì vậy, trong lần tải đầu tiên sẽ mất nhiều thời gian để tải xuống các file xuống, vì vậy, việc tối ưu hóa là rất cần thiết

Đối với phương pháp tốt nhất, các lập trình viên web sẽ chia các gói lớn thành các gói nhỏ hơn vì nó cho phép các nhà phát triển tải các tệp theo kiểu lazy load và cải thiện hiệu suất của ứng dụng React.

Dưới đây là snippet của bản build production cho ứng dụng React:

Bạn có thể tạo một bản build production bằng cách chạy lệnh build npm run build hoặc yarn build - các files .js.css trong thư mục build/static/jsbuild/static/css tương ứng.

Từ hình ảnh trên, chúng ta có thể thấy rằng các files được chia thành nhiều chunks khác nhau. create-react-app đạt được điều này với plugin SplitChunksPlugin trong webpack.

Hãy phân tích đoạn mã trong hình trên:

Main.[hash].chunk.css đại diện cho tất cả các mã CSS mà ứng dụng của chúng ta cần. Lưu ý rằng ngay cả khi bạn viết CSS bằng JavaScript bằng cách sử dụng styled-components, nó vẫn sẽ được biên dịch sang CSS

Number.[hash].chunk.js đại diện cho tất cả các thư viện được sử dụng trong ứng dụng. Về cơ bản, đó là tất cả các vendor codes được impoet từ thư mục node_modules

Main.[hash].chunk.js là tất cả các files ứng dụng - App.js, Contact.js, About.js, v.v. Nó đại diện cho tất cả mã đã viết cho ứng dụng React của mình

Runtime-main.[hash].js đại diện cho một logic thời gian chạy webpack nhỏ được sử dụng để tải và chạy ứng dụng. Nội dung của nó nằm trong file build/index.html theo mặc định

Tuy nhiên, ngay cả khi bản build production được tối ưu hóa, vẫn còn chỗ để cải thiện.

Cùng xem xét hình ảnh bên dưới:

Mặc dù chúng ta có thể tạo một bản build production và triển khai ứng dụng như hiện tại, nhưng hình ảnh trên cho thấy rằng nó vẫn có thể được tối ưu hóa hơn nữa.

Từ hình ảnh, chúng ta thấy rằng file main.[hash].chunk.js chứa tất cả các files ứng dụng và có kích thước 1000KB.

Chúng ta cũng có thể thấy rằng khi người dùng truy cập /login, toàn bộ đoạn coder 1000KB sẽ được trình duyệt tải xuống. Đoạn này chứa code mà người dùng có thể không bao giờ cần.

Do đó, nếu trang /login2KB, người dùng sẽ phải tải một đoạn 1000KB để xem một trang chỉ có 2KB.

Vì kích thước main.[hash].chunk.js tăng lên khi ứng dụng được phát triển, các ứng dụng lớn hơn có thể vượt quá kích thước 1000KB, có nghĩa là thời gian tải ứng dụng sẽ có thể tăng đáng kể - và nó có thể hoạt động chậm hơn nếu người dùng có tốc độ internet kém. Đây là lý do tại sao chúng ta cần tối ưu hóa hơn nữa.

Giải pháp cho việc này là chia main.[hash].chunk.js thành các phần nhỏ hơn, điều này đảm bảo rằng khi người dùng truy cập trang web, họ chỉ tải xuống phần mã mà họ cần. Trong ví dụ này, trình duyệt của người dùng chỉ nên tải đoạn /login

Bằng cách đó, chúng ta sẽ giảm đáng kể số lượng code mà người dùng tải xuống trong lần tải đầu tiên của ứng dụng và tăng hiệu suất ứng dụng lên đáng kể.

Hãy xem cách triển khai code splitting trong luôn nào

Implementing route-centric code splitting

Để implement code splitting, chúng ta cần kết hợp cả những kĩ thuật của JavascriptReact nữa

1. Dynamic imports

Đây là một tính năng JavaScript hiện đại giúp nhập các filé gần giống như một promise

Before:

import Login from "Pages/Login.js";
import Home from "Pages/Home.js";
import About from "Pages/About.js";
import Contact from "Pages/Contact.js";
import Blog from "Pages/Blog.js";
import Shop from "Pages/Shop.js";

After:

Các đoạn mã ở trên import các files bằng cách static import.

Khi webpack gặp cú pháp này, nó sẽ gộp tất cả các files lại với nhau.

Sau:

const module = await import('/modules/myCustomModule.js');

Không giống như static import - đó là import đồng bộ, dynamic import là import không đồng bộ. Điều này cho phép import các modules và files của mình theo yêu cầu request.

Khi webpack gặp cú pháp này, nó ngay lập tức bắt đầu code splitting.

2. React.lazy()

Các Component React này là một function lấy một function khác làm đối số. Đối số này gọi một phép dynamic import và trả về promise.

React.lazy() xử lý promise này và yêu cầu nó trả về một modules có chứa React component trong export default (Export của mỗi files).

Before:

import Login from "Pages/Login.js";

After:

import React, {lazy} from "react";
const Login = lazy(()=> import("Pages/Login"));

Trang đăng nhập hiện lazy-loaded, đảm bảo rằng đoạn Login.js chunk chỉ được tải khi nó được hiển thị.

  • Error boundaries

Nếu các module đấy hoặc các modules không tải được (ví dụ: do lỗi mạng), nó sẽ gây ra lỗi toàn bộ.

Bạn có thể xử lý các lỗi này bằng cách hiển thị thông báo cho người dùng để trải nghiệm của người dùng tốt hơn và quản lý việc khôi phục bằng Error boundaries.

Khi bạn đã tạo Error boundaries, bạn có thể sử dụng nó ở bất kỳ đâu phía trên các Lazy Component của mình để hiển thị trạng thái lỗi khi có lỗi mạng xảy ra, v.v.

3. React.Suspense()

React.Suspense() cho phép tạm dừng có điều kiện việc hiển thị một thành phần cho đến khi nó được tải.

Nó có một fallback prop, nó được chấp nhận như một phần tử React. Phần tử React này có thể là một đoạn code JSX hoặc một component hoàn chỉnh.

Khi người dùng truy cập trang sử dụng dynamic import, họ có thể thấy màn hình trắng trong khi ứng dụng tải modules.

Đôi khi người dùng thậm chí có thể gặp lỗi do dynamic export không đồng bộ. Khả năng xảy ra điều này sẽ tăng lên nếu người dùng có kết nối internet chậm.

React.lazy()React.suspense() được sử dụng cùng nhau để giải quyết vấn đề này.

Trong khi React.Suspense tạm ngừng hiển thị component cho đến khi tất cả các thành phần phụ thuộc của nó được tải xuống, nó cũng hiển thị phần tử fallback props dưới dạng giao diện tải xuống (đây là lúc icon loading được hiển thị để người dùng biết rằng việc tải trang vẫn đang diễn ra).

Hãy xem xét đoạn mã dưới đây:

import React, { lazy, Suspense } from 'react';

const Hero = lazy(() => import('./Components/Hero'));
const Service = lazy(() => import('./Component/Service'));

const Home = () => {
  return (
    <div>
      <Suspense fallback={<div>Page is Loading...</div>}>
        <section>
          <Hero /> 
          <Service />
        </section>
      </Suspense>
    </div>
  );
}

Ở đây, chúng ta lazy-load Components/Hero và các Compoents/Service. Đây là những phụ thuộc của component Home. Nó cần chúng để hiển thị một trang chủ hoàn chỉnh.

Chúng ta sử dụng Suspense component để tạm ngừng hiển thị thành phần chính cho đến khi các phần phụ thuộc được lazy load để người dùng không gặp lỗi hoặc trang trống khi họ điều hướng đến homepage.

Bây giờ khi một component đang được lazy load, người dùng sẽ tương tác với giao diện fallback UI bên dưới:

<div>Page is Loading...</div>

4. react-router-dom

Thư viện react-router-dom cũng hỗ trợ code splittng.

Nó cho phép tải xuống các chunks ở mức route level.

Hãy xem xét đoạn mã dưới đây:

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Home = lazy(() => import('./routes/Home'));
const Shop = lazy(() => import('./routes/Shop'));

const App = () => {
    return ( 
    <Router>
      <Suspense fallback={<div>Page is Loading...</div>}>
        <Switch>
          <Route exact path="/" component={Shop}/>
          <Route path="/shop" component={Shop}/>
        </Switch>
      </Suspense>
    </Router>
  )
};

Từ đoạn code mẫu trên, chúng ta đã thiết lập các route của mình bằng cách sử dụng thư viện react-router-dom, và HomeCompoentShopComponent được lazy load.

Do thiết lập của chúng ta, webpack sẽ phân đoạn mã của chúng ta thành các chunk. Vì vậy, người dùng chỉ nhận được các đoạn chunks cần thiết để hiển thị một trang theo yêu cầu.

Ví dụ: khi người dùng truy cập homepage, người dùng nhận được đoạn chunk của Home.js và khi người dùng truy cập shop page, họ sẽ thấy đoạn chunk Shop.js.

Do đó, chúng ta đã giảm được đáng kể thời gian tải ban đầu của ứng dụng, ngay cả khi không giảm số lượng code trong ứng dụng của mình.

Conclusion

Trong bài viết này, chúng ta đã giải thích phần code splittinglà gì và tại sao việc sử dụng lại hữu ích.

Chúng ta cũng đã thảo luận về việc tận dụng dynamic import, React.lazy()Suspense để tạo ra một ứng dụng React hoạt động tốt hơn.

Hi vọng chia sẽ của mình giúp ích được cho mọi người

References

Error boundaries

Chunk file and webpack

Code splitting Reactjs

Speed up Reactjs Application


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí