May 31, 2021 Article blog
The following article comes from the public number: Magician Kasong, author Kasong
As a React developer, can you answer the following two questions:
function App() {
const [num, updateNum] = useState(0);
window.updateNum = updateNum;
return num;
}
Can calling
window.updateNum(1)
update
0
in the view to
1
function App() {
const [num, updateNum] = useState(0);
function increment() {
setTimeout(() => {
updateNum(num + 1);
}, 1000);
}
return <p onClick={increment}>{num}</p>;
}
Quickly tap
p
5 times in 1 second, what does the view look like?
1. 可以
2. 显示为1
In fact, these two questions are essentially asking:
useState
save the state?
useState
update status?
This article combines the source code to cover the above two questions.
These are all you need to know about
useState
FunctionComponent
render
itself is just a function call.
So how does the
hook
called inside the
render
get the corresponding data?
Like what:
useState
gets
state
useRef
gets
ref
useMemo
gets cached data
The answer is:
Each component has a corresponding
fiber节点
(which can be understood as a
虚拟DOM
that holds information about the component.
Each time
FunctionComponent
render
the global variable
currentlyRenderingFiber
is assigned a
fiber节点
for that
FunctionComponent
Therefore, the inside of
hook
actually gets status information from
currentlyRenderingFiber
We know that there may be multiple
hook
in a
FunctionComponent
such as:
function App() {
// hookA
const [a, updateA] = useState(0);
// hookB
const [b, updateB] = useState(0);
// hookC
const ref = useRef(0);
return <p></p>;
}
How do so many
hook
get their own data?
The answer is:
A one-way list of
hook
data is saved in
currentlyRenderingFiber.memoizedState
As in the example above, it can be understood as:
const hookA = {
// hook保存的数据
memoizedState: null,
// 指向下一个hook
next: hookB
// ...省略其他字段
};
hookB.next = hookC;
currentlyRenderingFiber.memoizedState = hookA;
When
FunctionComponent
render
each
hook
is executed, the pointer to the
currentlyRenderingFiber.memoizedState
list moves backward once, pointing to the current
hook
corresponding data.
This is why
React
requires that
hook
cannot be called in a order that cannot be changed
hook
cannot be used in conditional statements) -- the
hook
corresponding data is obtained from a fixed-order list each time the order is
render
We know that
useState
returns the second argument of the array of values as the method of
changing the state.
In the source code, he is called
dispatchAction
Whenever
dispatchAction
called, an object update is created that
update
an update:
const update = {
// 更新的数据
action: action,
// 指向下一个更新
next: null
};
For examples below
function App() {
const [num, updateNum] = useState(0);
function increment() {
updateNum(num + 1);
}
return <p onClick={increment}>{num}</p>;
}
Calling
updateNum(num + 1)
creates:
const update = {
// 更新的数据
action: 1,
// 指向下一个更新
next: null
// ...省略其他字段
};
If
dispatchAction
is called more than once, for example:
function increment() {
// 产生update1
updateNum(num + 1);
// 产生update2
updateNum(num + 2);
// 产生update3
updateNum(num + 3);
}
update
forms a ringed list.
update3 --next--> update1
^ |
| update2
|______next_______|
Where is this list kept?
Since this
update
list is generated by a
dispatchAction
it is clear that the list belongs to the
useState hook
useState
We continue to supplement
hook
data structure.
const hook = {
// hook保存的数据
memoizedState: null,
// 指向下一个hook
next: hookForB
// 本次更新以baseState为基础计算新的state
baseState: null,
// 本次更新开始时已有的update队列
baseQueue: null,
// 本次更新需要增加的update队列
queue: null,
};
Where, the list of
update
for this update is saved in
queue
When
state
the ring list of
queue
is cut and mounted on the last face of
baseQueue
baseQueue
the new
state
based on
baseState
When the
state
is complete, the new
state
memoizedState
Why updates are not based on
memoizedState
butbaseState
state
because the state calculation process needs to consider priority, and someupdate
priorities may not be skipped. SomemoizedState
is not necessarily the same asbaseState
Back to our opening question:
function App() {
const [num, updateNum] = useState(0);
window.updateNum = updateNum;
return num;
}
Can calling
window.updateNum(1)
update
0
in the view to
1
We need to look at the implementation of the
updateNum
approach here:
updateNum === dispatchAction.bind(null, currentlyRenderingFiber, queue);
As you can see,
updateNum
method binds the
dispatchAction
of
currentlyRenderingFiber
to
queue
hook.queue
As described above, the purpose of calling
dispatchAction
is to generate
update
and insert them into the
hook.queue
list.
Now that
queue
is bound to
dispatchAction
as a preset parameter, the call
dispatchAction
is limited to
FunctionComponent
The second question
function App() {
const [num, updateNum] = useState(0);
function increment() {
setTimeout(() => {
updateNum(num + 1);
}, 1000);
}
return <p onClick={increment}>{num}</p>;
}
Quickly tap
p
5 times in 1 second, what does the view look like?
We know that calling
updateNum
produces
update
where the transpose
update.action
Click 5 times in 1 second.
When you click on the fifth time, the update
update
by the first click has not entered the update process, so
hook.baseState
has not changed.
The
update
generated by these five clicks are all based on the same
baseState
calculation of the new
state
and
num
variable has not changed (i.e. 5
update.action
(i.e.,
num + 1
is the same value).
Therefore, the final rendering result is 1.
So how do you change the view from 1 to 5 with 5 clicks?
From what we know above, we need to change
baseState
or
action
baseState
determined by
React's
update process, we have no control over it.
But we can control
action
action
can pass not only
值
but also
函数
// action为值
updateNum(num + 1);
// action为函数
updateNum(num => num + 1);
In the process of generating a new
state
based on
baseState
and
update
lists:
let newState = baseState;
let firstUpdate = hook.baseQueue.next;
let update = firstUpdate;
// 遍历baseQueue中的每一个update
do {
if (typeof update.action === 'function') {
newState = update.action(newState);
} else {
newState = action;
}
} while (update !== firstUpdate)
It can be seen that when passing
值
since we
action
same value for action 5 times, the
newState
that is ultimately calculated is also the same value.
When passing
函数
newState
is calculated five times based on the
action
function, and the result is finally added up.
If in this example, we use
useReducer
of
useState
because
useReducer
action
is always a
函数
we will not encounter the problem in our example.
In fact,
useState
itself is a
useReducer
preset with the following
reducer
function basicStateReducer(state, action) {
return typeof action === 'function' ? action(state) : action;
}
Through this article, we learned about the full execution of
useState
That's what
W3Cschool编程狮
has to say
about useState,
and I hope it will help you.