Phân chia mã bằng React.lazy và Suspense

Bạn không bao giờ cần gửi nhiều mã hơn mức cần thiết cho người dùng, vì vậy, hãy chia các gói của bạn để đảm bảo điều này không bao giờ xảy ra!

Phương thức React.lazy giúp bạn dễ dàng phân tách mã ứng dụng React ở cấp thành phần bằng cách sử dụng tính năng nhập động.

import React, { lazy } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));

const DetailsComponent = () => (
  <div>
    <AvatarComponent />
  </div>
)

Tại sao thông tin này hữu ích?

Một ứng dụng React lớn thường bao gồm nhiều thành phần, phương thức tiện ích và thư viện bên thứ ba. Nếu bạn không cố gắng tải các phần khác nhau của ứng dụng chỉ khi cần, thì một gói JavaScript lớn sẽ được gửi đến người dùng ngay khi họ tải trang đầu tiên. Điều này có thể ảnh hưởng đáng kể đến hiệu suất của trang.

Hàm React.lazy cung cấp một cách tích hợp để tách các thành phần trong một ứng dụng thành các đoạn JavaScript riêng biệt mà không cần nhiều thao tác. Sau đó, bạn có thể xử lý các trạng thái tải khi ghép nối với thành phần Suspense.

Suspense (Hồi hộp)

Vấn đề khi gửi tải trọng JavaScript lớn đến người dùng là thời gian cần thiết để trang tải xong, đặc biệt là trên các thiết bị và kết nối mạng yếu hơn. Đây là lý do tại sao tính năng phân tách mã và tải lười lại cực kỳ hữu ích.

Tuy nhiên, người dùng sẽ luôn phải trải qua một độ trễ nhỏ khi một thành phần phân tách mã đang được tìm nạp qua mạng. Vì vậy, điều quan trọng là phải hiển thị trạng thái tải hữu ích. Việc sử dụng React.lazy với thành phần Suspense sẽ giúp giải quyết vấn đề này.

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

const AvatarComponent = lazy(() => import('./AvatarComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
  </Suspense>
)

Suspense chấp nhận thành phần fallback cho phép bạn hiển thị bất kỳ thành phần React nào dưới dạng trạng thái đang tải. Ví dụ sau đây cho thấy cách hoạt động của tính năng này. Hình đại diện chỉ được hiển thị khi người dùng nhấp vào nút, sau đó hệ thống sẽ tạo một yêu cầu để truy xuất mã cần thiết cho AvatarComponent bị tạm ngưng. Trong thời gian chờ đợi, thành phần tải dự phòng sẽ xuất hiện.

Trong đây, mã tạo nên AvatarComponent có kích thước nhỏ, đó là lý do vòng quay tải chỉ hiển thị trong một khoảng thời gian ngắn. Các thành phần lớn hơn có thể mất nhiều thời gian hơn để tải, đặc biệt là trên các kết nối mạng yếu.

Để minh hoạ rõ hơn về cách hoạt động của tính năng này:

  • Để xem trước trang web, hãy nhấn vào Xem ứng dụng. Sau đó, nhấn vào biểu tượng Toàn màn hình toàn màn hình.
  • Nhấn tổ hợp phím `Ctrl+Shift+J` (hoặc `Command+Option+J` trên máy Mac) để mở DevTools.
  • Nhấp vào thẻ Mạng.
  • Nhấp vào trình đơn thả xuống Điều tiết. Theo mặc định, trình đơn này được đặt thành Không điều tiết. Chọn 3G tốc độ cao.
  • Nhấp vào nút Click Me (Nhấp vào tôi) trong ứng dụng.

Giờ đây, chỉ báo tải sẽ hiển thị lâu hơn. Hãy lưu ý cách tất cả mã tạo nên AvatarComponent được tìm nạp dưới dạng một phần riêng biệt.

Bảng điều khiển mạng của DevTools hiển thị một tệp chunk.js đang được tải xuống

Tạm ngưng nhiều thành phần

Một tính năng khác của Suspense là cho phép bạn tạm ngưng tải nhiều thành phần, ngay cả khi tất cả các thành phần đều được tải lười.

Ví dụ:

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

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
    <InfoComponent />
    <MoreInfoComponent />
  </Suspense>
)

Đây là một cách cực kỳ hữu ích để trì hoãn việc kết xuất nhiều thành phần trong khi chỉ hiển thị một trạng thái tải. Sau khi tất cả các thành phần hoàn tất quá trình tìm nạp, người dùng sẽ thấy tất cả các thành phần đó hiển thị cùng một lúc.

Bạn có thể thấy điều này với mã nhúng sau:

Nếu không có chỉ báo này, bạn có thể dễ dàng gặp phải vấn đề tải luân phiên hoặc các phần khác nhau của giao diện người dùng tải lần lượt, mỗi phần có chỉ báo tải riêng. Điều này có thể khiến trải nghiệm người dùng trở nên khó chịu hơn.

Xử lý lỗi tải

Suspense cho phép bạn hiển thị trạng thái tải tạm thời trong khi các yêu cầu mạng được thực hiện trong nền. Nhưng nếu các yêu cầu mạng đó không thành công vì lý do nào đó thì sao? Có thể bạn đang không có kết nối mạng hoặc ứng dụng web của bạn đang cố gắng tải lười một URL có phiên bản đã lỗi thời và không còn hoạt động sau khi triển khai lại máy chủ.

React có một mẫu chuẩn để xử lý linh hoạt các loại lỗi tải này: sử dụng ranh giới lỗi. Như mô tả trong tài liệu, bất kỳ thành phần React nào cũng có thể đóng vai trò là ranh giới lỗi nếu thành phần đó triển khai một (hoặc cả hai) phương thức vòng đời static getDerivedStateFromError() hoặc componentDidCatch().

Để phát hiện và xử lý các lỗi tải lười, bạn có thể gói thành phần Suspense bằng một thành phần mẹ đóng vai trò là ranh giới lỗi. Bên trong phương thức render() của ranh giới lỗi, bạn có thể hiển thị các phần tử con như hiện tại nếu không có lỗi hoặc hiển thị thông báo lỗi tuỳ chỉnh nếu có lỗi:

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

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {hasError: false};
  }

  static getDerivedStateFromError(error) {
    return {hasError: true};
  }

  render() {
    if (this.state.hasError) {
      return <p>Loading failed! Please reload.</p>;
    }

    return this.props.children;
  }
}

const DetailsComponent = () => (
  <ErrorBoundary>
    <Suspense fallback={renderLoader()}>
      <AvatarComponent />
      <InfoComponent />
      <MoreInfoComponent />
    </Suspense>
  </ErrorBoundary>
)

Kết luận

Nếu bạn không chắc nên bắt đầu áp dụng tính năng phân tách mã cho ứng dụng React từ đâu, hãy làm theo các bước sau:

  1. Bắt đầu ở cấp tuyến. Tuyến là cách đơn giản nhất để xác định các điểm trong ứng dụng có thể được phân tách. Tài liệu về React cho biết cách sử dụng Suspense cùng với react-router.
  2. Xác định mọi thành phần lớn trên một trang trên trang web của bạn chỉ hiển thị khi người dùng tương tác theo một số cách nhất định (chẳng hạn như nhấp vào một nút). Việc phân tách các thành phần này sẽ giảm thiểu tải trọng JavaScript.
  3. Hãy cân nhắc việc tách mọi nội dung khác nằm ngoài màn hình và không quan trọng đối với người dùng.