-2

Concurrent React Mode: Using Suspense and useTransition to Build A Better UX

Bài viết được dịch từ nguồn: https://hackernoon.com/concurrent-react-using-suspense-and-usetransition-to-build-better-ux-cman2cdd

Nhóm nhà phát triển React JS đã công bố một số thay đổi thú vị vài tháng trước - React sẽ nhận được Concurrent Mode. Về cơ bản, điều này sẽ cho phép React thực hiện đồng thời nhiều giao diện người dùng. Tất nhiên, JavaScript là một luồng đơn và đồng thời thực sự là một illusion, nhưng các tính năng mới sẽ cho phép các ứng dụng web (và Ứng dụng gốc một khi các tính năng này dùng React Native) phản ứng nhanh và linh hoạt hơn so với hiện tại đỡ mất công hơn và mã tùy chỉnh từ nhà phát triển để thực hiện điều này.

Concurrent Mode hiện có sẵn trong bản dựng thử nghiệm của React, vì vậy hãy tìm hiểu và xem cách sử dụng API mới.

Trong bài viết này tôi sẽ chỉ cho bạn cách sử dụng Suspense API với useTransition hook. Một dạng hook khác, useDeferredValue, phục vụ một mục đích hơi khác, nhưng không kém phần quan trọng.

But what about the existing Suspense feature?

Bạn sẽ nhận thấy rằng API Suspense đã có mặt trong React kể từ phiên bản 16.6. Trên thực tế, đây là cùng một API đang được mở rộng để làm nhiều hơn trong bản dựng thử nghiệm React. Trong React 16.6, Suspense chỉ có thể được sử dụng cho một mục đích: chia tách code và tải các thành phần một cách lazy loading bằng cách sử dụng React.lazy ().

The New Way - Render as you fetch

Điều này đã được thảo luận rất nhiều thông qua các cuộc nói chuyện và blog và trong tài liệu chính thức, vì vậy tôi sẽ nói ngắn gọn - Concurrent React cho phép chúng tôi triển khai mẫu render as you fetch, mà renders component như dữ liệu cần thiết để điền chúng được tìm nạp đồng thời. React renders càng nhiều càng tốt mà không cần dữ liệu có sẵn và renders component yêu cầu dữ liệu được tìm nạp ngay khi dữ liệu có sẵn. Trong thời gian này, các thành phần này được cho là suspended.

The Setup

Đối với điều này, tôi đang sử dụng React app cơ bản mà tôi đã định cấu hình thủ công với WebpackBabel, với sự khác biệt duy nhất đang chạy:

npm i react@experimental react-dom@experimental --save

Để có được các phiên bản thử nghiệm thay vì cài đặt release versions của reactreact-dom.

Opting in to Concurrent Mode

Concurrent Mode thay đổi cách React xử lý các thành phần một cách cơ bản, bạn sẽ cần thay đổi dòng ReactDOM.render() trong index.js của bạn thành:

ReactDOM.createRoot(document.getElementById('root')).render(<App />);

Điều này cho phép chế độ đồng thời trong ứng dụng của bạn.

Tôi cũng đã thiết lập App.js để hiển thị một thành phần có tên là Data.

import React from 'react';
import Data from './Data';

const App = () => {
    return (
        <div>
            <p>React Concurrent Mode testing</p>
            <Data />
        </div>
    );
}

export default App;

The Demo

Bây giờ tạo Data.js

import React, { useState, useTransition, Suspense } from 'react';
import DataDisplay from './DataDisplay';
import { dataFetcher } from './api';

const initialData = { read: () => { return { foo: "initial" } } };

const Data = () => {
    const [data, setData] = useState(initialData);
    const [count, setCount] = useState(0);
    const [startDataTransition, isDataPending] = useTransition({ timeoutMs: 2000 });

    const fetchNewData = () => {
        startDataTransition(() => {
            setData(dataFetcher())
        })
    }

    return (
        <div>
            <Suspense fallback={<p>Loading...</p>}>
                <DataDisplay data={data} />
                <button disabled={isDataPending} onClick={() => fetchNewData()}>Click me to begin data fetch</button>
            </Suspense>
            <p>Counter: {count}</p>
            <button onClick={() => { setCount(count + 1); }}> Click me to check if the app is still responsive</button>
        </div>

    )
}

export default Data;

dataFetcher là một hàm trả về object đặc biệt cho phép React biết statesobject này có thể được fetched khi components phụ thuộc vào state này rendered. Các components này suspends nếu dữ liệu chưa được tìm nạp xong. Chúng ta sẽ xem xét cách tạo object đặc biệt.

initData hiển thị định dạng của object được dataFetcher trả về khi dữ liệu đã tải xong. Nó có chức năng đọc trả về đối tượng với dữ liệu chúng ta cần. Lý tưởng nhất là initData nên triển khai một số loại chức năng lưu trữ cho dữ liệu được tải lần cuối, nhưng ở đây chúng tôi chỉ sử dụng {foo: initial}.

Một state mà trong khi update/fetched khiến một thành phần bị treo, phải được cập nhật bằng hook useTransition. Hook này trả về một cặp giá trị - một hàm có chức năng gọi lại trong đó bạn đặt trạng thái và boolean cho chúng tôi biết khi nào quá trình chuyển đổi đang diễn ra.

Đối số được truyền cho useTransition là một đối tượng cho React biết phải đợi bao lâu trước khi tạm dừng thành phần. Để hiểu nó, hãy nghĩ về nó theo cách này: Chúng tôi có một số dữ liệu trên màn hình và chúng tôi đang tìm nạp một số dữ liệu mới để thay thế nó. Chúng tôi muốn hiển thị spinner trong khi dữ liệu mới đang được tìm nạp, nhưng người dùng có thể thấy dữ liệu cũ trong một giây hoặc có thể là nửa giây trước khi spinnerđược hiển thị. Sự chậm trễ này được đề cập trong đối tượng này.

Điều này rất hữu ích trong các trường hợp khi hiển thị dữ liệu cũ cho đến khi dữ liệu mới được tải là mong muốn và cũng để ngăn spinner hiển thị trong một phần giây (gây ra lỗi như jitter) trong các hoạt động tìm nạp dữ liệu nhanh.

Chúng ta hãy xem xét kỹ hơn về Suspense:

<Suspense fallback={<p>Loading...</p>}>
    <DataDisplay data={data} />
    <button disabled={isDataPending} onClick={() => fetchNewData()}>Click me to begin data fetch</button>
</Suspense>

Bất kỳ thành phần nào sẽ bị treo đều được bọc bên trong Suspense component. Bên trong fallback props, chúng tôi chuyển component sẽ được hiển thị thay vào đó trong khi component bên trong đang chờ dữ liệu. Đây thường là một spinner hoặc loading indicator của một số loại để chỉ ra trực quan cho người dùng một cái gì đó đang xảy ra, vì vậy nó không xuất hiện như thể trang không phản hồi với nhấp chuột.

Ở đây tôi đã sử dụng boolean isDataPending để vô hiệu hóa nút trong khi dữ liệu đang được tìm nạp, ngăn người dùng nhấn nút nhiều lần và gửi nhiều yêu cầu.

Tất cả thành phần JavaScript trong trang tiếp tục hoạt động trong khi thành phần bị treo và dữ liệu đang được tìm nạp. Bộ đếm để tăng nó có thể được sử dụng để xác nhận điều này.

DataDisplay là một thành phần đơn giản lấy dữ liệu và gọi chức năng đọc của nó và hiển thị kết quả.

import React, { memo } from 'react';

const DataDisplay = ({ data }) => {
    return (
        <h3>{data.read().foo}</h3>
    )
}

export default memo(DataDisplay);

Cuối cùng, chúng tôi xem xét DataFetcher và những thứ khác bên trong api.js

export const dataFetcher = (params) => {
    return wrapPromise(fetchData(params))
}

const wrapPromise = (promise) => {
    let status = "pending";
    let result;
    let suspender = promise.then(
        r => {
            status = "success";
            result = r;
        },
        e => {
            status = "error";
            result = e;
        }
    );
    return {
        read() {
            if (status === "pending") {
                throw suspender;
            } else if (status === "error") {
                throw result;
            } else if (status === "success") {
                return result;
            }
        }
    };
}

const fetchData = (params) => {
    // In a real situation, use params to fetch the data required.
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({
                foo: 'bar'
            })
        }, 3000);
    });
}

wrapPromise chịu trách nhiệm cho mọi thứ được tích hợp với React và nên đơn giản nếu bạn đã sử dụng Promise trước đó. Nó trả về kết quả nếu quá trình tìm nạp thành công, raise lỗi nếu có và ném Promise với trạng thái "đang chờ xử lý" nếu thao tác chưa hoàn thành.

Cảm ơn và hi vọng bài viết có ích trong công việc của bạn


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í