This article has been posted on Github: github.com/beichensky/… Welcome Star, welcome Follow!

preface

This article describes the use of the Suspense component and the new SuspenseList component in React 18 and the related properties. It also compares it to the pre-18 version and introduces some of the advantages of the new features.

Rethink the use of Suspense

In React 16, it was possible to use React. Lazy to break code down in Suspense, so let’s take a look at how it was used.

  1. In writing the User component, network requests are made in the User component, and data is obtained

    User.jsx

    import React, { useState, useEffect } from 'react';
    
    // Network request to get user data
    const requestUser = id= >
        new Promise(resolve= >
            setTimeout(() = > resolve({ id, name: ` user${id}`.age: 10 + id }), id * 1000));const User = props= > {
        const [user, setUser] = useState({});
    
        useEffect(() = > {
            requestUser(props.id).then(res= > setUser(res));
        }, [props.id]);
     
        return <div>The current user is {user.name}</div>;
    };
    
    export default User;
    Copy the code
  2. Load User component in App component via React. Lazy (use it wrapped in girl component)

    App.jsx

    import React from "react";
    import ReactDOM from "react-dom";
    
    const User = React.lazy(() = > import("./User"));
    
    const App = () = > {
        return (
            <>
                <React.Suspense fallback={<div>Loading...</div>} ><User id={1} />
                </React.Suspense>
            </>
        );
    };
    
    ReactDOM.createRoot(document.getElementById("root")).render(<App />);
    Copy the code
  3. Effect:

  4. At this point, you can see that the User component is loading before loading. Although the code is split, there are two fly in the ointment

    • A series of actions need to be performed in the User component: define state, send a request in effect, then modify state, trigger render

    • While loading is shown, only the components are loaded, and the internal requests and actual data that the user wants to see are not processed

    Ok, with those two questions in mind, let’s move on.

The realization principle of Suspense

Internal processes

  • Suspense makes subcomponents wait before rendering and show fallback content while waiting

  • Component subtrees in Suspense have a lower priority than other parts of the component tree

  • Execute the process

    • You can request data asynchronously in the Render function

    • React is going to read from our cache

    • If the cache hits, render directly

    • If there is no cache, a PROMISE exception is thrown

    • When the promise is finished, React will render again and show the data

    • Fully synchronous writing, without any asynchronous callback

Simple version of code implementation

  • A PROMISE exception is thrown when the child component is not loaded

  • Listen for a promise, update the state when the state changes, trigger component updates, and rerender the child components

  • Display child component content

import React from "react";

class Suspense extends React.Component {
    state = {
        loading: false};componentDidCatch(error) {
        if (error && typeof error.then === "function") {
            error.then(() = > {
                this.setState({ loading: true });
            });
            this.setState({ loading: false}); }}render() {
        const { fallback, children } = this.props;
        const { loading } = this.state;
        returnloading ? fallback : children; }}export default Suspense;

Copy the code

3. The new version of the User component

Let’s change our User component to address the two issues we mentioned above

const User = async (props) => {
    const user = await requestUser(props.id);
    return <div>The current user is {user.name}</div>;
};
Copy the code

I wish the User component could be written this way, saving a lot of redundant code and showing the Fallback consistently before the request is complete

However, we cannot write components directly with async and await. What to do?

Using the principles of Suspense implementation described above, you can encapsulate a layer of promises that you throw as exceptions in your requests to complete and show the results.

Meaning of the wrapPromise function:

  • Accept a promise as an argument

  • Define promise states and results

  • Returns an object containing the read method

  • When the read method is called, it determines whether to throw an exception or return a result based on the promise’s current state.

function 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") {
                returnresult; }}}; }Copy the code

Rewrite the User component using wrapPromise

// Network request to get user data
const requestUser = (id) = >
    new Promise((resolve) = >
        setTimeout(
            () = > resolve({ id, name: ` user${id}`.age: 10 + id }),
            id * 1000));const resourceMap = {
    1: wrapPromise(requestUser(1)),};const User = (props) = > {
    const resource = resourceMap[props.id];
    const user = resource.read();
    return <div>The current user is {user.name}</div>;
};
Copy the code

In this case, you can see that loading is displayed on the interface first, and data is displayed directly after the request ends. There is no side effect code to write and no loading judgment to perform within the component.

Four, SuspenseList

You’ve talked about how to use Suspense above, but what if you want to control the order in which you show more than one girl in Suspense?

A new component is also available in React: SuspenseList

SuspenseList properties

The SuspenseList component accepts three attributes

  • RevealOrder: Load order for Suspense

    • Forwards: shown front to back, no matter how fast or slow the request is, it will wait for forwards

    • Backwards: Backwards, no matter how fast the request is, waiting for the next one to be displayed first

    • Together: All Suspense shows simultaneously when ready

  • Tail: Specifies how to show unready Suspense in SuspenseList

    • Not set: All Suspense fallbacks are loaded by default

    • Collapsed: shows only the next fallback in Suspense in the list

    • Hidden: Unready project is not limited to any information

  • Children: child elements

    • The child can be any React element

    • When a child element contains a nonSuspenseComponent and is not settailProperty, then all of theSuspenseElements must be loaded simultaneously, setrevealOrderProperty is also invalid. When settingtailAttributes after eithercollapsedorhidden.revealOrderAttribute takes effect

    • Suspense multiple children do not block each other

SuspenseList use

The User component

import React from "react";

function 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") {
                returnresult; }}}; }// Network request to get user data
const requestUser = (id) = >
    new Promise((resolve) = >
        setTimeout(
            () = > resolve({ id, name: ` user${id}`.age: 10 + id }),
            id * 1000));const resourceMap = {
    1: wrapPromise(requestUser(1)),
    3: wrapPromise(requestUser(3)),
    5: wrapPromise(requestUser(5)),};const User = (props) = > {
    const resource = resourceMap[props.id];
    const user = resource.read();
    return <div>The current user is {user.name}</div>;
};

export default User;
Copy the code

App component

import React from "react";
import ReactDOM from "react-dom";

const User = React.lazy(() = > import("./User"));
// You can use the following import method instead of the React. Lazy ()
// import User from "./User"

const App = () = > {
    return (
        <React.SuspenseList revealOrder="forwards" tail="collapsed">
            <React.Suspense fallback={<div>Loading...</div>} ><User id={1} />
            </React.Suspense>
            <React.Suspense fallback={<div>Loading...</div>} ><User id={3} />
            </React.Suspense>
            <React.Suspense fallback={<div>Loading...</div>} ><User id={5} />
            </React.Suspense>
        </React.SuspenseList>
    );
};

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
Copy the code

The effect after using SuspenseList

A link to the

  • In this paper,wrapPromiseMethods fromDan Abramov 的 frosty-hermann-bztrp

Afterword.

Suspense and SuspenseList components in React are used in Suspense and SuspenseList. All the code has been posted in the use of SuspenseList section. If there is any doubt, let it be known and discuss it together.

The article has written wrong or not rigorous place, welcome everyone to be able to put forward the valuable opinion, very grateful.

If you like it or help, welcome to Star.