8
头图

Hello everyone, I'm Casson.

For this common interaction step as follows:

  1. Click the button to trigger a status update
  2. Component render
  3. view rendering

Which steps do you think have room for performance optimization and ?

The answers are: 1 and 2.

For step 1 , if there is no change before and after the status update, you can skip the remaining steps. This optimization strategy is called eagerState .

For step 2 , if the descendant node of the component has no state change, you can skip render of descendant component. This optimization strategy is called bailout .

It seems that the logic of eagerState is very simple, just need to compare whether there is a change in before and after the state update.

However, in practice it is complicated.

This article answers a question by understanding the logic of eagerState : Has the performance optimization of React reached the extreme?

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

a strange example

Consider the following components:

function App() {
  const [num, updateNum] = useState(0);
  console.log("App render", num);

  return (
    <div onClick={() => updateNum(1)}>
      <Child />
    </div>
  );
}

function Child() {
  console.log("child render");
  return <span>child</span>;
}
Online Demo address

First render, print:

App render 0
child render 

Click div for the first time, print:

App render 1
child render 

Click div for the second time, print:

App render 1

The third, fourth...click div , do not print

In the second hit of App render 1 , is printed and child render is not printed. Descendent components representing App do not have render and hit bailout .

the third and subsequent clicks of on 1622ad05b28eb9, nothing is printed, which means there is no component render , and eagerState is hit.

Then the question comes, obviously the first and second clicks are executed updateNum(1) , obviously the state has not changed, why did not hit eagerState the second time?

Trigger condition of eagerState

First of all we need to understand, why is it called eagerState (urgent state)?

Usually, when can I get the latest status of ? When component render .

When component render , useState executes and returns latest state.

Consider the following code:

const [num, updateNum] = useState(0);

The useState returned after num is executed is the latest state of .

The reason why the latest state of can only be calculated when useState is executed is because the state of is calculated based one or more updates of from 1622ad05b28fa3.

For example, trigger 3 updates in the following click event:

const onClick = () => {
  updateNum(100);
  updateNum(num => num + 1);
  updateNum(num => num * 2);
}

What should be the latest status of num of in component render ?

  • First num becomes 100
  • 100 + 1 = 101
  • 101 * 2 = 202

So, useState will return 202 as the latest state of num.

The actual situation will be more complicated. update has its own priority, so it is impossible to determine which updates will participate in the state calculation render .

So, in this case the component must render , useState must execute to know what the latest state of num is.

Then it is impossible to compare the latest state of num with the current state of num in advance to determine whether the state of has changed .

The significance of eagerState is that in a certain situation of , we can calculate the latest state of in advance before the component render (this is the origin of eagerState ).

In this case, the component does not need render to compare whether the state of has changed .

So what is the situation?

The answer is: there is no time to update on on the current component.

When the update does not exist, this update is the first update for the component. The latest status of can be determined when there is only one update.

So, the premise of eagerState is:

There is no update for the current component, so when the state update is triggered for the first time, the latest state of can be calculated immediately, and then compared with the current state of .

If the two are consistent, the subsequent process of render is omitted.

This is the logic of eagerState . But unfortunately, the actual situation is even more complicated.

Let's first look at an example of a seemingly irrelevant with .

Necessary React source code knowledge

For the following components:

function App() {
  const [num, updateNum] = useState(0);
  window.updateNum = updateNum;

  return <div>{num}</div>;
}

Execute the following code in the console, can you change the num displayed by the view?

window.updateNum(100)

The answer is: yes.

Because the App component corresponding to fiber (the node that saves the component-related information) has been passed to as the default parameter of window.updateNum :

// updateNum的实现类似这样
// 其中fiber就是App对应fiber
const updateNum = dispatchSetState.bind(null, fiber, queue);

Therefore, when updateNum is executed, it can obtain App corresponding to fiber .

However, a component actually has 2 fiber , they:

  • One saves the relevant information corresponding to the current view of , called current fiber
  • One saves the relevant information corresponding to the view that will change next, called wip fiber

updateNum is preset to wip fiber .

When the component triggers an update, it will mark the update fiber on both corresponding to the .

When the component is render , useState will execute, calculate the new state of , and clear the update flag wip fiber on .

When the view is rendered, current fiber and wip fiber will swap positions (that is, current fiber in this update will become wip fiber in the next update).

back to example

Just mentioned, the premise of eagerState is: current component does not exist to update .

Specifically, there is no update for current fiber and wip fiber corresponding to the components.

Back to our example:

Click div for the first time, print:

App render 1
child render 

current fiber and wip fiber marked the update at the same time.

After render wip fiber 's update flag cleared.

At this time current fiber also has update mark .

After rendering, current fiber and wip fiber will swap places.

It becomes: wip fiber update exists, current fiber update does not exist.

So when you click div for the second time, due to the update of eagerState , wip fiber is not hit, so print:

App render 1

The render update flag of is cleared after wip fiber .

The update marker does not exist on both fiber at this time. Therefore, subsequent clicks div will trigger eagerState , and the component will not trigger render .

Summarize

Due to the mutual influence of various parts inside React , the results of performance optimization of React sometimes confuse developers.

Why haven't many people complained? Because the performance optimization will only be reflected in the indicators and will not affect the interaction logic.

Through this article, we found that the performance optimization of React did not achieve the ultimate goal. Due to the existence of two fiber and eagerState strategies, the optimal state was not achieved.


卡颂
3.1k 声望16.7k 粉丝