The text/swim deer

React 16 -> 17 -> 18

Probably because there were so many incompatible changes in Act15 to 16 that it was a pain for developers to upgrade, for a long time React developers didn’t release new versions, but instead integrated new capabilities on V16. 16.3/16.8/16.12 had interesting new features almost every few releases.

After two and a half years of v16, the React team released V17 and announced that it was intended to be a transitional version of v16 with the main goal of reducing the cost of upgrades to subsequent versions. Prior to V17, different React versions could not be used together. One important reason was that the event delegate was attached to the Document in previous versions. With V17, the event delegate was attached to the DOM container at the root of the React tree, which made it possible for multiple React versions to coexist. React 17+ allows you to mix React 17+ and React 17+ with v17, v18, v19, etc.

Increasingly, the React developers are focusing on “incremental updates”. After only two minor releases of V17, an alpha of V18 is available, and users need to make minimal or even no changes to make existing React apps work on V18. So what are the new changes and features in V18?

Note: The content of this article is from some discussion of ReactwG/ReacT-18, which was translated and written by the author. If you do not understand the place, welcome to the comments section of the exchange, discussion, correction ~

React 18

The update strategy for Act18 is “incremental upgrades,” where new capabilities, including the famously concurrent rendering, are optional and don’t immediately cause any noticeable disruptive changes in component behavior.

You can upgrade to React 18 with minimal or no changes to your application code, with a level of effort comparable to a typical major React release.

You’ll be able to upgrade directly to React 18 with almost no changes to the code in your app, and it won’t be any more difficult to upgrade than previous React versions.

**React官网 ** reactjs.org/blog/2021/0…

Concurrent rendering is an important architectural upgrade of React. The advantage of concurrent rendering is to improve the performance of React APP. When you use some of act18’s new features, you may already be using concurrent rendering.

The new Root API

In Act18, reactdom.render () officially became Legacy and a new RootAPI reactdom.createroot () was added, with the following usage differences:

import ReactDOM from'the react - dom;import App from 'App';

ReactDOM.render(<App />.document.getElementById('root'));
Copy the code
import ReactDOM from'the react - dom;import App from 'App';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(<App />);
Copy the code

As you can see, with the new API, you can create multiple root nodes for a React App, and even create them with different versions of React in the future. React18 retains both of these usages, and old projects can still use reactdom.render () if they don’t want to change; New projects that want to improve performance can take advantage of concurrent rendering with reactdom.createroot ().

Automatic Batching

What is a Batching

Batching is when React groups multiple state updates into a single re-render for better performance.

For better application performance, React combines multiple state updates into a single render. Act17 only merges status updates during browser events such as clicks. Act18 also merges status updates after the event handler has occurred. Here’s an example:

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClickPrev() {
   	setCount(c= > c - 1); // Does not re-render yet
    setFlag(f= >! f);// Does not re-render yet
    // React will only re-render once at the end (that's batching!)
  }
  
  function handleClickNext() {
    fetchSomething().then(() = > {
      // React 17 and earlier does NOT batch these because
      // they run *after* the event in a callback, not *during* it
      setCount(c= > c + 1); // Causes a re-render
      setFlag(f= >! f);// Causes a re-render
    });
  }

  return (
    <div>
      <button onClick={handleClickPrev}>Prev</button>
      <button onClick={handleClickNext}>Next</button>
      <h1 style={{ color: flag ? "blue" : "black}} ">{count}</h1>
    </div>
  );
}
Copy the code

What is Automatic Batching

In React 18, you can enjoy automatic batching directly by using the new Root API reactdom.createroot () method! Here are some scenarios for automatic updates:

Do not use automatic batching

Batching is safe, but there are special cases where you don’t want batching to happen, such as when you need to read the data on the new DOM immediately after the status update. Use reactdom.flushsync () in this case:

import { flushSync } from 'react-dom'; // Note: react-dom, not react

function handleClick() {
  flushSync(() = > {
    setCounter(c= > c + 1);
  });
  // React has updated the DOM by now
  flushSync(() = > {
    setFlag(f= >! f); });// React has updated the DOM by now
}
Copy the code

Impact on Hooks/Classes

  • Hooks have no effect
  • There is no effect on Classes for the most part, focusing on one mode: whether a state value is read between setStates. The differences are as follows:
handleClick = () = > {
  setTimeout(() = > {
    this.setState(({ count }) = > ({ count: count + 1 }));

    // Print {count: 1, flag: false} before React17
    // in React18, it prints {count: 0, flag: false}
    console.log(this.state);

    this.setState(({ flag }) = > ({ flag: !flag }));
  });
};
Copy the code

If you don’t want to fix it by tweaking the code logic, you can just use reactdom.flushsync () :

handleClick = () = > {
  setTimeout(() = > {
    ReactDOM.flushSync(() = > {
      this.setState(({ count }) = > ({ count: count + 1 }));
    });

    // in React18, it prints {count: 1, flag: false}
    console.log(this.state);

    this.setState(({ flag }) = > ({ flag: !flag }));
  });
};
Copy the code

New Suspense SSR architecture

Suspense differences in Suspense 16/18

Suspense came out as an experimental API way back in Suspense 16 and is more intuitive than older versions of Legacy Suspense.

React says there are minor differences between the two versions, but since Suspense is implemented concurrently, it’s still a Breaking change. Here are some of the differences:

<Suspense fallback={<Loading />} ><ComponentThatSuspends />
  <Sibling />
</Suspense>
Copy the code
Older versions Legacy Suspense The new version is Concurrent Suspense
In terms of performance
  • **<Sibling>**** Components are immediately rendered into DOM **
  • Effects/Lifecycles are triggered immediately
  • See the React DEMO
  • **<Sibling>**Components are not immediately rendered into the DOM
  • The Effects/Lifecycles is in**<ComponentThatSuspends>**Resolve will not be triggered until the component completes data parsing
  • See the React Demo
  • In principle
  • When rendering to<ComponentThatSuspends>“, it will be skipped<Sibiling>The component will be rendered to the DOM Tree, but it will be setdisplay: hiddenuntil<ComponentThatSuspends>Resovle will appear.
  • All etc.<Suspense>The entire tree is committed simultaneously in a single, consistent batch only after the data in resolve is resolved. From a development perspective, the new version<Suspense>Behavior is more predictable because it can be predicted.
  • Problems with existing SSR architectures

    There is not much explanation about the existing SSR architecture principle. The problem lies in that everything is serial, and the latter task cannot be started before the completion of any prior task, that is, “All or Nothing”, which is generally the following process:

    1. The server gets data internally
    2. The server renders HTML internally
    3. The client loads the code remotely
    4. The client begins to hydrate (hydration) |

    React18 new strategy

    Suspense **React18 provides Suspense that breaks through this serialization limitation and optimizes front-end load speeds and interactionable wait times. ** This SSR architecture relies on two new features:

    • Server-side “streaming HTML” : Use the APIpipeToNodeWritable 
    • “Selective Hydration” for the client: Use<Suspense> 

    What makes the new version of Suspense SSR faster? Take the following structure for example:

    Current HTML

    <Layout>
      <NavBar />
      <Sidebar />
      <RightPane>
        <Post />
        <Suspense fallback={<Spinner />} ><Comments />
        </Suspense>
      </RightPane>
    </Layout>
    Copy the code

    As for the previous page structure,

    is retrieved asynchronously through the interface, which is slow to request data, so we wrap it in Suspense>. In a normal SSR architecture, you can only proceed to the next step after

    is loaded. In the new mode, the first content returned by the HTML stream will not have the HTML information related to the

    component. Instead, the HTML of

    will be returned:



    <main>
      <nav>
        <! --NavBar -->
        <a href="/">Home</a>
       </nav>
      <aside>
        <! -- Sidebar -->
        <a href="/profile">Profile</a>
      </aside>
      <article>
        <! -- Post -->
        <p>Hello world</p>
      </article>
      <section id="comments-spinner">
        <! -- Spinner -->
        <img width=400 src="spinner.gif" alt="Loading..." />
      </section>
    </main>
    Copy the code

    After the server gets the data from

    , React sends the HTML to the same stream. React creates an ultra-small inline

    <div hidden id="comments">
      <! -- Comments -->
      <p>First comment</p>
      <p>Second comment</p>
    </div>
    <script>
      // This implementation is slightly simplified
      document.getElementById('sections-spinner').replaceChildren(
        document.getElementById('comments'));</script>
    Copy the code

    So unlike a traditional HTML stream, it doesn’t have to happen in a top-down order.

    Selective Hydration

    Code splitting is a common technique, and we can use react. lazy to unpack some code from the main package.

    import { lazy } from 'react';
    
    const Comments = lazy(() = > import('./Comments.js'));
    
    // ...
    
    <Suspense fallback={<Spinner />} ><Comments />
    </Suspense>
    Copy the code

    Before Act18, React.lazy didn’t support server-side rendering, and even the most popular solutions offered a choice between “don’t use SSR for code splitting” and “Use SSR but only hydratie after all JS loads are complete.”

    In Update 18, children wrapped in Suspense can delay hydratie. This behavior is automatically removed in React, so react.lazy also supports SSR by default.

    New features startTransition

    To solve what problem?

    Using this API prevents the execution of internal functions from slowing down the UI response time.

    Take a query selector: a user enters a keyword, requests remote data, and presents the search results.

    // Urgent: Show what was typed
    setInputValue(input);
    
    // Not urgent: Show the results
    setSearchQuery(input);
    Copy the code

    Users expect immediate feedback when typing text, while queries and displaying results allow for delays (in fact, developers often use artificial tricks to make them delay updates, such as debounce)

    After the introduction of startTransition:

    import { startTransition } from 'react';
    
    // Urgent: Show what was typed
    setInputValue(input);
    
    // Mark any state updates inside as transitions
    startTransition(() = > {
      // Transition: Show the results
      setSearchQuery(input);
    });
    Copy the code

    What is Transition?

    Updates fall into two categories:

    • Urgent Updates: actions that intuitively require immediate response, such as typing, clicking, dragging, etc., and that feel like something has gone wrong if they are not responded to immediately;
    • Transition Updates: Transitions the UI from one view to another. It requires no immediate response, and some latency is within expectations and acceptable.

    In React, most Updates are conceptually called Transition Updates. However, Transition is optional for backward compatibility, so the default update mode in React18 is Still Urgent Updates. To use Transition Updates, wrap the function in startTransition.

    What is the difference between startTransition and setTimeout?

    There are two main points:

    1. StartTransition handles render updates earlier than setTimeout, and the difference is slightly noticeable on some of the less efficient machines. At runtime, startTransition executes immediately like a normal function, except that all updates from the startTransition function are marked as “transition”. React uses this flag as a reference when processing updates.
    2. Large screen updates within setTimeout lock the page, during which the user cannot interact with the page. Updates labeled “Transition” are interruptible,

    When to use

    • Slow rendering: Some React reactions require complex rendering that takes a lot of time
    • Slow network: Time-consuming network requests