Code-Splitting

Đóng Gói (Bundling)

Hầu hết files trong các ứng dụng React sẽ được “đóng gói” bằng cách sử dụng những công cụ như Webpack, Rollup hay Browserify. Đóng gói là quá trình xử lý những files đã được import và kết hợp chúng thành một file duy nhất: File đóng gói này sau đó có thể được trang web tải lên chỉ một lần.

Ví Dụ

App:

// app.js
import { add } from './math.js';

console.log(add(16, 26)); // 42
// math.js
export function add(a, b) {
  return a + b;
}

Bundle:

function add(a, b) {
  return a + b;
}

console.log(add(16, 26)); // 42

Chú ý:

Bundle của bạn có thể sẽ trông rất khác bên trên.

Nếu bạn đang sử dụng Create React App, Next.js, Gatsby, hay một công cụ tương tự, bạn sẽ được thiết lập sẵn webpack để đóng gói ứng dụng của mình.

Nếu không, bạn sẽ cần phải tự thiết lập. Ví dụ, tham khảo Cách cài đặtLàm thê nào để bắt đầu sử dụng hướng dẫn ở tài liệu Webpack.

Phân chia Code

Đóng gói hẵn rất tuyệt vời, nhưng khi ứng dụng của bạn trở nên lớn hơn, file đóng gói của bạn cũng sẽ lớn theo. Đặc biệt khi bạn sử dụng third-party library (thư viện bên thứ 3) lớn. Bạn cần phải cẩn thận với những đoạn code bạn đang include vào bundle của mình, bằng cách đó bạn sẽ không vô tình làm nó trở nên quá lớn khiến ứng dụng mất nhiều thời gian để tải.

Để tránh việc nhận được một bundle lớn, tốt nhất nên bắt đầu “splitting (chia nhỏ)” gói bundle của bạn. Code-Splitting là một feature hỗ trợ bởi bundler như Webpack, Rollup và Browserify (via factor-bundle) nó có thể tạo ra nhiều bundle nhỏ có thể được load một cách tự động tại thời điểm runtime.

Phân chia code cho ứng dụng giúp “lazy-load” chỉ những phần người dùng đang cần, tăng đáng kể hiệu suất mà không cần phải giảm số lượng code trong ứng dụng, bạn đã tránh phải tải những đoạn code người dùng có thể sẽ không bao giờ cần đến, và giảm số lượng code cần tải lên trong lần đầu tiên.

import()

Phương pháp tốt nhất để sử dụng code-splitting trong ứng dụng là thông qua cú pháp import() động.

Trước:

import { add } from './math';

console.log(add(16, 26));

Sau:

import("./math").then(math => {
  console.log(math.add(16, 26));
});

Chú ý:

Cú pháp import() động là một đoạn ECMAScript (JavaScript) proposal hiện tại chưa được xem như một phần tiêu chuẩn của ngôn ngữ. Nó mong đợi sẽ được chấp nhận trong tương lai gần.

Khi Webpack chạy đến cú pháp này, nó sẽ tự động phân chia code trong ứng dụng của bạn. Nếu bạn sử dụng Create React App, việc này đã được thiết lập sẵn cho bạn và bạn có thể bắt đầu sử dụng ngay. Nó cũng được hỗ trợ sẵn trong Next.js.

Nếu bạn đang tự mình cấu hình Webpack, bạn có thể sẽ muốn tham khảo Webpack’s hướng dẫn phân chia code. Cấu hình Webpack của bạn có thể sẽ trông mơ hồ như thế này.

Khi sử dụng Babel, bạn sẽ cần phải chắc chắn rằng Babel có thể phân giải cú pháp import động nhưng không làm nó bị biến đổi. Bạn sẽ cần babel-plugin-syntax-dynamic-import.

React.lazy

Chú ý:

React.lazy và Suspense chưa có sẵn cho server-side rendering. Nếu bạn muốn phân chia code ở những ứng dụng render tại server, chúng tôi xin giới thiệu Loadable Components. Nó có hướng dẫn phân chia code với server-side rendering.

Chức năng React.lazy cho phép bạn render một import động như một component bình thường.

Trước:

import OtherComponent from './OtherComponent';

Sau:

const OtherComponent = React.lazy(() => import('./OtherComponent'));

Nó sẽ tự động tải bundle có chứa OtherComponent khi component này được được render lần đầu tiên.

React.lazy chỉ lấy một function mà nó được gọi import() động. Nó phải trả về một Promise và phân giải thành một module với một default export có chứa một React component.

Lazy component nên được render bên trong một Suspense component, điều này cho phép chúng ta thể hiện vài nội dung fallback cho người dùng (ví dụ như một loading indicator) trong khi chời đợi lazy component được load.

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

Thuộc tính fallback chấp nhận bất kỳ React elements nào bạn muốn render trong khi chờ component được tải lên. Bạn có thể đặt Suspense component bất kỳ nơi nào bên trên lazy component. Bạn thậm chí có thể bọc nhiều lazy component với duy nhất một Suspense component.

const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

Error boundaries

Nếu OtherComponent không thể tải lên (Ví dụ, lỗi mạng), nó sẽ kích hoạt lỗi. Bạn có thể điều khiển những lỗi đó để hiển thị một trải nghiệm người dùng tốt hơn và quản lý phục hồi với Error Boundaries. Một khi bạn đã tạo Error Boundary, bạn có thể sử dụng nó bất kỳ nơi nào bên trên lazy components của bạn để hiển thị thông báo lỗi khi có sự cố về mạng.

import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

const MyComponent = () => (
  <div>
    <MyErrorBoundary>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </MyErrorBoundary>
  </div>
);

Phân chia code dựa vào định tuyến(Route-based)

Việc quyết định nơi nào cần phân chia code trong ứng dụng của bạn có thể sẽ gặp một chút khó khăn. Bạn muốn chắc chắn những nơi bạn chọn sẽ đều nhau, nhưng không phá vỡ trải nghiệm người dùng.

Một nơi tốt để bắt đầu là với routes. Hầu hết mọi người trên web đã quen với việc chuyển trang sẽ mất một khoảng thời gian nhất định. Bạn cũng có xu hướng render lại cả trang cùng một lần để ngăn người dùng không tương tác với những elements khác trong trang cùng một lúc.

Đây là một ví dụ hướng dẫn cách cài đặt ứng dụng của bạn phân chia code dựa trên route bằng cách sử dụng những thư viện như React Router with React.lazy.

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

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

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

Named Exports (Đặt tên Export)

React.lazy hiện tại chỉ hỗ trợ default export. Nếu module bạn muốn import sử dụng named export, bạn có thể tạo một module trung gian và sau đó export dưới dạng export default. Điều này chắc đảm bảo rằng tree shaking vẫn hoạt động và bạn không kéo những component chưa được sử dụng.

// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));