1
头图

Hello everyone, I'm Casson

React18 official version has been released for some time. If you upgrade to v18 and still use ReactDOM.render to create an application, you will receive the following alarm:

To the effect that: v18 use createRoot instead render create the application, if you still use render create the application, then the application will act Same as v17 .

React reason why the team has the confidence to let everyone upgrade to v18 and use createRoot is because they made a promise:

The general idea is: if you upgrade to v18 , as long as you don't use the concurrency feature (such as useTransition ), React will be the same as the previous version (the update will be synchronous and consistent with the previous version). uninterrupted)

What this article wants to say today is: In some cases, the above statement is wrong.

Welcome to join the human high-quality front-end framework group , with flying

Don't talk nonsense, the above example

There are examples a , b two states, for the first time rendering finished 2 seconds will trigger a , b update.

Among them, the way of triggering b update is special: simulated click, indirect trigger b update:

 function App() {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);
  const BtnRef = useRef<HTMLButtonElement>(null);

  useEffect(() => {
    setTimeout(() => {
      setA(9000);
      BtnRef.current?.click();
    }, 2000);
  }, []);

  return (
    <div>
      <button 
        ref={BtnRef} 
        onClick={() => setB(1)}>
        b: {b}
      </button>
      {Array(a).fill(0).map((_, i) => {
        return <div key={i}>{a}</div>;
      })}
    </div>
  );
}
full example address

Now we have two ways to mount <App/> .

v18 The way before:

 const rootElement = document.getElementById("root");

// v18之前创建应用的方式
ReactDOM.render(<App/>, rootElement);

v18 The way provided:

 const root = ReactDOM.createRoot(rootElement);

// v18创建应用的方式
root.render(
  <App />
);

To see the difference between the two, there are two ways:

  1. Increase the value in setA(9000) to make the page render more items. The more obvious the lag when the page is rendered, the more obvious the difference in rendering order.
 setTimeout(() => {
  setA(9000);
  BtnRef.current?.click();
}, 2000);
  1. react-dom.development.js in commitRootImpl method of ---b23ac9dbb1bfd11732d11b3a7dc012c7---

This method is React the method called when rendering, where you can see the order of page rendering by breaking the point.

For applications created by ReactDOM.render , the rendering sequence after triggering the update is as follows:

first:

Next:

For the application created by ReactDOM.createRoot , the rendering sequence after triggering the update is as follows:

first:

Next:

The rendering order has obviously changed, which is contrary to what the React documentation says.

What is the reason behind this?

Update priority, everywhere

First explain why in the following example b the update is indirectly triggered by triggering the onClick event :

 BtnRef.current?.click();

This is because: the updates triggered by different methods have different priorities , and the update triggered in onClick回调 is the highest priority, that is, the synchronization priority .

So here comes the question, v18 does not use the concurrency feature, shouldn't all updates be synchronous and uninterrupted ?

This is true, the update itself is synchronous and uninterruptible . But updates need to be scheduled.

In the example, if the app is created with ReactDOM.createRoot , the priority when triggering an update is as follows:

 setTimeout(() => {
  // 触发更新,优先级为“默认优先级”
  setA(9000);
  // 触发更新,优先级为“同步优先级”
  BtnRef.current?.click();
}, 2000);

Next React The execution flow is as follows:

  1. a Trigger update with priority "default priority"
  2. Schedule a with priority "default priority"
  3. b Trigger update with priority "sync priority"
  4. Scheduled update of b with priority "sync priority"
  5. At this time, it is found that there is already an update scheduled (the update of a ), and the priority is lower (default priority < synchronization priority)
  6. Cancel the update scheduling of a b instead
  7. The scheduling process ends and the synchronous, uninterruptible execution b
  8. b corresponding update is rendered into the page
  9. At this time, it is found that there is another update (the update of a ), schedule him
  10. The scheduling process ends, and the synchronous, uninterruptible execution a
  11. a corresponding update is rendered into the page

It can be seen that as long as ReactDOM.createRoot is used to create an application, the impact of priority will always exist. The difference from using the concurrency feature is:

  • Only default priority and sync priority
  • Priority only affects scheduling and does not interrupt the execution of updates

The historical baggage of older versions of React

So what about the execution order of applications created with ReactDOM.render ?

Remember a classic (and meaningless) React question: React update is synchronous or asynchronous?

In the following two cases, a print result is 1 ?

 // 情况1
onClick() {
  this.setState({a: 1});
  console.log(a);
}
// 情况2
onClick() {
  setTimeout(() => {
    this.setState({a: 1});
    console.log(a);
  })
}

Among them, in case 2 a the print result is 1 .

The reason why this happens is because of a React in the early implementation of 批处理 , not an intentional feature.

When React uses Fiber after the architecture is refactored, this flaw can be completely avoided. But in order to be consistent with the behavior of the old version, this is deliberately implemented.

So, in our example, these two updates will not be affected by the priority , but will be affected by the old version for compatibility :

 setTimeout(() => {
  setA(9000);
  BtnRef.current?.click();
}, 2000);

The execution flow of React is as follows:

  1. a Trigger the update, because it is triggered in setTimeout , so the subsequent update process will be executed synchronously
  2. a corresponding update is rendered into the page
  3. b Trigger the update, because it is triggered in setTimeout , so the subsequent update process will be executed synchronously
  4. b corresponding update is rendered into the page

Summarize

React As a framework that has been maintained for nearly 10 years, it is not easy to keep the framework behavior consistent after major version updates.

Changes in the update order have little effect on general applications.

However, if your application relies on the current value of the updated page to make subsequent judgments, then you need to pay attention to these subtle changes after upgrading to v18 .


卡颂
3.1k 声望16.7k 粉丝