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 của mình, vì vậy hãy chia các gói để đả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 điều này hữu ích?

Một ứng dụng React lớn thường sẽ bao gồm nhiều thành phần, phương thức tiện ích và thư viện của 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 thiết, thì một gói JavaScript lớn duy nhất sẽ được chuyển đến người dùng của bạn 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 cách tích hợp sẵn để tách các thành phần trong ứng dụng thành các phần JavaScript riêng biệt mà không cần tốn nhiều thao tác. Sau đó, bạn có thể quan tâm đến việc tải trạng thái khi kết hợp trạng thái này với thành phần Suspense.

Suspense (Hồi hộp)

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

Tuy nhiên, người dùng phải luôn có một chút độ trễ nhỏ khi tìm nạp một thành phần phân tách mã qua mạng, vì vậy, điều quan trọng là bạn 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 một thành phần fallback cho phép bạn hiển thị mọi thành phần React dưới dạng trạng thái tải. Ví dụ sau cho thấy cách hoạt động. Hình đại diện chỉ hiển thị khi người dùng nhấp vào nút này, sau đó một yêu cầu được thực hiện để 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ẽ hiển thị.

Ở đây, mã tạo nên AvatarComponent là nhỏ, đó là lý do vòng quay đang 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 tải hơn, đặc biệt là trên 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, hãy làm như sau:

  • Để xem trước trang web, hãy nhấn vào View App (Xem ứng dụng), sau đó nhấn vào Fullscreen toàn màn hình (Toàn màn hình).
  • Nhấn tổ hợp phím "Control+Shift+J" (hoặc "Command+Option+J" trên máy Mac) để mở Công cụ cho nhà phát triển.
  • Nhấp vào thẻ Mạng.
  • Nhấp vào trình đơn thả xuống Điều tiết (được điều chỉnh) thành Không điều tiết theo mặc định. Chọn 3G nhanh.
  • Nhấp vào nút Nhấp vào tôi trong ứng dụng.

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

Bảng điều khiển mạng Công cụ cho nhà phát triển cho thấy 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ả đều được tải từng phần.

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 duy nhất. Sau khi tất cả các thành phần đã tìm nạp xong, 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ể xem nội dung này bằng nội dung nhúng sau:

Nếu không, bạn sẽ dễ gặp sự cố tải so le hoặc các phần khác nhau của giao diện người dùng tải lần lượt, trong đó mỗi phần có chỉ báo tải riêng. Điều này có thể khiến người dùng có trải nghiệm 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 nâng cao. Nhưng nếu những yêu cầu mạng đó không thành công vì một lý do nào đó thì sao? Có thể bạn đang không kết nối mạng hoặc có thể ứng dụng web của bạn đang cố gắng tải từng phần một URL được tạo phiên bản đã lỗi thời và không còn dùng được sau khi triển khai lại máy chủ.

React có một mẫu tiê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, mọi thành phần React đều 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ý lỗi tải từng phần, 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ị phần tử con theo nguyên trạng nếu không có lỗi, hoặc hiển thị thông báo lỗi tuỳ chỉnh nếu có sự cố:

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 phân tách mã từ đâu cho ứng dụng React của mình, 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 có thể phân tách của ứng dụng. Tài liệu về phản ứng 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 thuộc trang web của bạn chỉ hiển thị khi có một số tương tác nhất định của người dùng (như nhấp vào một nút). Việc chia nhỏ các thành phần này sẽ giảm thiểu tải trọng JavaScript của bạn.
  3. Hãy cân nhắc tách những nội dung khác ngoài màn hình và không quan trọng đối với người dùng.