Hello everyone, I'm Casson.
For this common interaction step as follows:
- Click the button to trigger a
status update
- Component
render
- 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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。