What are the changes in React18?

What are the changes in React18?

Text/Youlu

React 16 -> 17 -> 18

It may be that there are too many incompatible changes from React 15 to 16, and it is quite painful for developers to upgrade. Therefore, for a long time, React developers did not release new versions, but integrated various new capabilities on v16, 16.3/16.8/16.12 Interesting new features appear almost every few versions.

After 2 and a half years of v16 version, the React team released v17, and at the same time announced that this version is positioned as a transitional version of a technical transformation. The main goal is to reduce the upgrade cost of subsequent versions . Before v17, different versions of React could not be mixed. One important reason is that in the previous version, the event delegate was attached to the document. Starting from v17, the event delegate is attached to the root DOM container that renders the React tree . This makes it more React. Coexistence of versions becomes possible. (Meaning that React 17+ can be mixed, the old page maintains v17, the new page uses v18 v19, etc.)

We can feel more and more that the developers of React have focused their upgrades on **"gradual upgrade"**. Only after the release of 2 minor versions of v17, the alpha of v18 appeared, and only users need to do it. Minimal and no changes are required to make existing React apps work on v18. So what are the new changes and new features in v18?

Note: The content of this article comes from part of the discussion of reactwg/react-18 , which was written after the author's translation and reading comprehension. If there is a place where the understanding is not in place, welcome to the big guys in the comment area to exchange, discuss, and correct~

React 18

React18's upgrade strategy is "gradual upgrade". New capabilities , including the well-known concurrent rendering , are optional, and will not immediately bring any obvious destructive changes to 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 can upgrade to React 18 directly without making any changes to the code in the application, and it will not be more difficult than the previous version of React.

**React official website** reactjs.org/blog/2021/0...

Concurrent rendering is an important architectural design upgrade at the bottom of React. The advantage of concurrent rendering is to improve the performance of React APP. After you use some of the new features of React18, you may have used concurrent rendering.

New Root API

In React18,

ReactDOM.render()
  Officially became Legacy, and added a new RootAPI
ReactDOM.createRoot()
, Their usage differences are as follows:

Import ReactDOM from 'DOM-REACT'; Import the App from 'the App' ; ReactDOM.render ( < the App/> , Document .getElementById ( 'the root' )); duplicated code
Import ReactDOM from 'DOM-REACT'; Import the App from 'the App' ; const root = ReactDOM.createRoot( document .getElementById( 'root' )); root.render( < App/> ); copy the code

As you can see, through the new API, we can create multiple root nodes for a React App, and even create it with different versions of React in the future. React18 retains the above two usages. Old projects can still be used if they don t want to be changed.

ReactDOM.render()
 ; New projects want to improve performance, you can use
ReactDOM.createRoot()
Take advantage of concurrent rendering.

Automatic batching

What is Batching

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

In order to achieve better performance of the application, React merges multiple state updates into one rendering . React17 will only merge state updates during browser events (such as clicks). React18 will also merge the status updates after the event handler occurs . for 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 code

What is Automatic Batching

In React 18, as long as the use of new Root API

ReactDOM.createRoot()
Method, you can directly enjoy the ability of automatic batching ! Here are some scenarios for automatic updates:

Do not use automatic batching

Batching is safe, but there are some special circumstances that do not want batching to happen, such as: you need to read the data on the new DOM immediately after the status is updated. In this case please use

ReactDOM.flushSync()
 (React officially does not recommend normalized use of this API):

Import {flushSync} from 'DOM-REACT' ; //Note: DOM-REACT, 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 code

Impact on Hooks/Classes

  • No effect on Hooks
  • In most cases, it has no effect on Classes. Focus on a mode: whether the state value is read between two setStates. The differences are as follows:
handleClick = () => { setTimeout ( () => { this .setState( ( {count} ) => ({ count : count + 1 })); //In React17 and before, it was printed as {count: 1, flag: false} //In React18, it was printed as {count: 0, flag: false} console .log( this .state); this .setState( ( {flag} ) => ({ flag : !flag })); }); }; Copy code

If you don t want to make corrections by adjusting the code logic, you can directly 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 code

New Suspense SSR architecture

The difference between Suspense in React16/18

Suspense came out as an experimental API as early as React16. Compared with the old version of Legacy Suspense, the new version of Concurrent Suspense is more in line with user intuition.

React officially said that there are relatively small differences between the two versions, but since the implementation of the new version of Suspense is based on concurrent rendering, this is still a Breaking Changes. Here are the differences:

<Suspense fallback={ < Loading/> }> < ComponentThatSuspends/> < Sibling/> </Suspense> Copy code
Legacy Suspense old versionNew version of Concurrent Suspense
From the performance point of view
  • **<Sibling>**
    ** The component is immediately rendered into the DOM **
  • Effects/Lifecycles will be triggered immediately
  • Refer to React official DEMO
  • **<Sibling>**
    ** The component is not immediately rendered into the DOM **
  • Effects/Lifecycles is in
    **<ComponentThatSuspends>**
    The component will be triggered after the data analysis (resolve in English) is completed
  • Refer to React official Demo
  • From the principle point of view
  • When rendered to
    <ComponentThatSuspends>
    When it will be skipped
    <Sibiling>
    The component will be rendered into the DOM Tree, but it will be set
    display: hidden
     until
    <ComponentThatSuspends>
    Resovle will appear.
  • Wait for all
    <Suspense>
    After all the data in is resolved, the entire tree will be submitted at the same time in a single, consistent batch. From a development perspective, the new version
    <Suspense>
    The behavior is more in line with expectations because it can be predicted.
  • Problems with existing SSR architecture

    The principle of the existing SSR architecture is not explained much. The problem is that everything is serial, and the latter task cannot be started before any previous task is completed, that is, "All or Nothing" . The general process is as follows:

    1. Get data inside the server
    2. Render HTML inside the server
    3. Client loads code from remote
    4. The client starts to hydrate (hydrate)|

    React18 new strategy

    **React18 provides Suspense, which breaks this serial limitation and optimizes the loading speed of the front-end and the waiting time required for interaction. **This SSR architecture relies on two new features:

    • Server-side "Streaming HTML": Use API
      pipeToNodeWritable
       
    • "Selective Hydration" of the client: use
      <Suspense>
       

    What is the reason why the new version of Suspense SSR is faster? Take the following structure as an example:

    Streaming HTML

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

    As in the previous page structure,

    <Comments>
    It is obtained asynchronously through the interface. The data request in this process is relatively slow, so we wrap it in
    <Suspense>
     in. In the ordinary SSR architecture, generally you can only wait
    <Comments>
    The next step can only be carried out after loading in. In the new mode, there will be no content in the first returned content of the HTML stream
    <Comments>
     Component related HTML information, replaced by
    <Spinnger>
     HTML:

    < Main > < NAV > <-! The NavBar -> < A the href = "/" > Home </A > </NAV > < aside > <-! Sidebar -> < A the href = "/Profile" > Profile </a > </aside > < article > <!-- Post --> < p > Hello world </p > </article > < section ID = "Comments-Spinner" > <-! Spinner -> < IMG width = 400 the src = "spinner.gif" Alt = "Loading ..."/> </sectionTop > </main > copy the code

    Wait for the server to get it

    <Comments>
     After the data, React then adds the
    <Comments>
     HTML information, sent through the same stream (stream), React will create a super small inline

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

    So unlike traditional HTML streaming, it does not have to happen in a top-down order.

    Selective Hydration

    Code splitting is our common method, we can use

    React.lazy
      Remove part of the code from the main package.

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

    Before React18,

    React.lazy
    Server-side rendering is not supported. Even the most popular solution allows everyone to choose one of "do not use SSR for code splitting" and "use SSR but hydratie only after all js has been loaded."

    In the React18 version, it was

    <Suspense>
      The packaged sub-components can postpone the hydratie, this behavior is automatically done internally by React, so
    React.lazy
    SSR is also supported by default .

    New feature startTransition

    In order to solve the problem?

    Using this API can prevent the execution of internal functions from slowing down the UI response speed .

    Take the query selector as an example: the user enters keywords, requests remote data, and displays search results.

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

    Users want to get immediate feedback when entering text, and query and display results are allowed to be delayed (in fact, developers often artificially use some means to delay updates, such as debounce)

    After the introduction of startTransition, the usage is:

    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 code

    What is transition?

    Updates can be divided into two categories:

    • Urgent Updates: For example, typing, clicking, dragging, etc., behaviors that intuitively require immediate response. If you do not respond immediately, it will feel like something went wrong;
    • Transition Updates: Transition the UI from one view to another. It does not require immediate response, and a little delay is within the expected range and acceptable.

    In fact, in React applications, most updates should be Transition Updates conceptually, but for backward compatibility considerations, transition is optional, so the default update method in React18 is still Urgent Updates. I want to use it. Transition Updates can use functions

    startTransition
      Wrap it up.

    What is the difference between startTransition and setTimeout?

    There are two main points:

    1. startTransition more earlier than setTimeout rendering update process, this perception a little obvious difference in the number of poor performance of the machine. At runtime, startTransition is the same as a normal function, which is executed immediately, except that all updates brought about by the function execution will be marked as "transition", and React will use this mark as a reference when processing updates .
    2. Large screen updates within setTimeout will lock the page, during which time the user cannot interact with the page. The updates marked as "transition" can be interrupted,

    When to use

    • Slow rendering: some React complex rendering that takes a lot of time
    • Slow network: time-consuming network requests