I. Introduction
This article mainly react-hooks
, from the perspective of principle and source code, and began to analyze the react-hooks
. I believe that after this article, the hooks
problems during the interview will be solved. The actual react-hooks
is not so difficult to understand. It sounds very cool
. In fact, the functional component solves a technical solution that does not have state
, life cycle, and logic cannot reuse .
Hook is a new feature of React 16.8. It allows you to use state and other React features without writing classes.
The old rules, 🤔️🤔️🤔️ we started today’s discussion with questions ( can answer the last few, you can try it yourself, master ):
- 1 When the function context of the stateless component is executed every time,
react
hooks
record the state of 0608e782ad2376? - 2 What is the use of
react-hooks
to record the order ofhooks
Ask another way! Why can't you declarehooks
conditional statement?hooks
statement at the top of the component? - 3
function
function componentsuseState
, andclass
class componentssetState
What is the difference? - 4 How does
react
hooks
, which is inside the function component? - 5
useEffect
,useMemo
, whyuseRef
can access the latest changed value without dependency injection? - 6
useMemo
cache the value? How to apply it to optimize performance? - 7 Why
useState
passed twice is the same, and the function component is not updated? - ...
If you read this article carefully, all these problems will be solved.
The essential difference between function component and class component
In explanation react-hooks
before principle, we want to deepen their understanding about, function class components and assemblies in the end what is the difference , ado, we look at two code fragments.
class Index extends React.Component<any,any>{
constructor(props){
super(props)
this.state={
number:0
}
}
handerClick=()=>{
for(let i = 0 ;i<5;i++){
setTimeout(()=>{
this.setState({ number:this.state.number+1 })
console.log(this.state.number)
},1000)
}
}
render(){
return <div>
<button onClick={ this.handerClick } >num++</button>
</div>
}
}
Print the result?
Let's take a look at the function component:
function Index(){
const [ num ,setNumber ] = React.useState(0)
const handerClick=()=>{
for(let i=0; i<5;i++ ){
setTimeout(() => {
setNumber(num+1)
console.log(num)
}, 1000)
}
}
return <button onClick={ handerClick } >{ num }</button>
}
Print the result?
------------Announce the answer-------------
In the first example 🌰 prints the result: 1 2 3 4 5
In the second example 🌰 prints the result: 0 0 0 0 0
The real problem is to deceive, we have to analyze together, first class components, due to the implementation of setState
not in react
execution on the execution context of normal function, but setTimeout
performed, batch updates condition is destroyed. I won’t talk about the principle here, so you can get the changed state
.
But in stateless components, it doesn't seem to take effect. The reason is simple. In the class
state, an instantiated class
to maintain various states in the function
component, there is no state to save this information. Every time the function context is executed, all variables and constants are Re-declare, complete the execution, and then be recycled by the garbage mechanism. So as above, no matter setTimeout
executed, it is executed in the current function context. At this time, num = 0
will not change, and then setNumber
will be executed. After the function component is executed again, num
change.
Therefore, for the class
component, we only need to instantiate it once, and the state
state of the component is saved in the instance. For each update, you only need to call the render
method. But in the function
component, each update is a new function execution. In order to save some state, execute some side-effect hooks, react-hooks
came into being to help record the state of the component and handle some additional side effects.
Second first acquaintance: lift the veil of hooks
1 What happened when we introduced hooks?
We start with the introduction of hooks
, taking useState
as an example, when we write this from the project:
import { useState } from 'react'
So we go to useState
to see which god it is?
react/src/ReactHooks.js
useState
export function useState(initialState){
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
useState()
implementation of equal dispatcher.useState(initialState)
there is the introduction of a dispatcher
, we look at resolveDispatcher
done?
resolveDispatcher
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current
return dispatcher
}
ReactCurrentDispatcher
react/src/ReactCurrentDispatcher.js
const ReactCurrentDispatcher = {
current: null,
};
We see that ReactCurrentDispatcher.current
initialized as null
, and then there is no more text. We can only write down ReactCurrentDispatcher
for the time being. See ReactCurrentDispatcher
is used?
2 Start the creation, starting with the function execution of stateless components
To fully understand hooks
, we must start from its roots. When we introduced hooks
, we ended up with a ReactCurrentDispatcher
. The clues are all broken, so we can only start from the execution of the function component.
renderWithHooks execution function
When is the function
component executed?
react-reconciler/src/ReactFiberBeginWork.js
function
component initialization:
renderWithHooks(
null, // current Fiber
workInProgress, // workInProgress Fiber
Component, // 函数组件本身
props, // props
context, // 上下文
renderExpirationTime,// 渲染 ExpirationTime
);
For initialization, there is no current
tree. After a component update is completed, the current workInProgress
tree will be assigned to the current
tree.
function
component update:
renderWithHooks(
current,
workInProgress,
render,
nextProps,
context,
renderExpirationTime,
);
We can see from the top, renderWithHooks
function that is called call function
component functions main function. Let's focus on what renderWithHooks
done?
renderWithHooks react-reconciler/src/ReactFiberHooks.js
export function renderWithHooks(
current,
workInProgress,
Component,
props,
secondArg,
nextRenderExpirationTime,
) {
renderExpirationTime = nextRenderExpirationTime;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.expirationTime = NoWork;
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props, secondArg);
if (workInProgress.expirationTime === renderExpirationTime) {
// ....这里的逻辑我们先放一放
}
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
renderExpirationTime = NoWork;
currentlyRenderingFiber = null;
currentHook = null
workInProgressHook = null;
didScheduleRenderPhaseUpdate = false;
return children;
}
all components perform the function, here are methods , first of all we should understand a few gratitude, which we understand to follow useState
is helpful.
current fiber tree: When a rendering is completed, a
current
tree will be generated, and current
will be replaced with a real Dom
tree commit
workInProgress fiber tree
fiber
tree to be reconciled and rendered. Once the new component is updated, a current
will be copied as workInProgress
. After the update is completed, the current workInProgress
tree will be assigned to the current
tree.
workInProgress.memoizedState
: In the class
component, memoizedState
stores the state
information, and in the function
component, can be disclosed in advance, and memoizedState
is stored in the form of a linked list of information hooks
workInProgress.expirationTime
: react
uses different expirationTime
to determine the priority of the update.
currentHook
: It can be understood that hooks
node pointed to by the current
workInProgressHook
: Understand hooks
node pointed to by the workInProgress
renderWithHooks
main function of the function:
memoizedState
and updateQueue
workInProgress
tree that will be reconciled and rendered. Why do this? Because in the execution of the next function component, the new hooks
information should be mounted on these two attributes, and then in the component commit
stage , Replace the workInProgress
tree with the current
tree, and replace the real DOM
element node. And save hooks
information in the current
Then according to whether the current function component is rendered for the first time, assign ReactCurrentDispatcher.current
different from hooks
, and finally associate it with ReactCurrentDispatcher
For the first rendering of the component, the HooksDispatcherOnMount
hooks object is used. For the function component that needs to be updated after rendering, it is the HooksDispatcherOnUpdate
object, so the two differences are current
by whether memoizedState
(hook information) is on the 0608e782ad3dc0 tree. If current
does not exist, it proves to be the first rendering of the function component.
Next, calls Component(props, secondArg);
execute our function component. Our function component is actually executed here. Then, the hooks
we wrote is executed in turn, and the hooks
information is sequentially saved to the workInProgress
tree. As for how it is stored, we will talk about it shortly.
Next, it is also very important to assign ContextOnlyDispatcher
to ReactCurrentDispatcher.current
. Since js
is single-threaded, that is to say, we are not in the function component. The hooks
calls are all ContextOnlyDispatcher
objects on the hooks
object. 0608e782ad3eba, let's see what ContextOnlyDispatcher
is .
const ContextOnlyDispatcher = {
useState:throwInvalidHookError
}
function throwInvalidHookError() {
invariant(
false,
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
' one of the following reasons:\n' +
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
'2. You might be breaking the Rules of Hooks\n' +
'3. You might have more than one copy of React in the same app\n' +
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
);
}
It turns out that react-hooks
uses this function component to perform the assignment of different hooks
objects to determine whether the hooks
is inside the function component, catching and throwing an exception.
Finally, reset some variables such as currentHook
, currentlyRenderingFiber
, workInProgressHook
etc.
3 different hooks
objects
hooks
objects are called in the first rendering component and update component of the function. Let's take a look at HooksDispatcherOnMount
and HooksDispatcherOnUpdate
.
first rendering (I only show the commonly used hooks
):
const HooksDispatcherOnMount = {
useCallback: mountCallback,
useEffect: mountEffect,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
};
update component:
const HooksDispatcherOnUpdate = {
useCallback: updateCallback,
useEffect: updateEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState
};
It seems that for the first rendering component and the updated component, react-hooks
uses two sets of Api
. The second and third parts of this article will focus on the connection between the two.
We use a flowchart to describe the entire process:
17AC0A26-745A-4FD8-B91B-7CADB717234C.jpg
Three hooks are initialized, what will happen to the hooks we write
This article will focus on the four focus hooks
expansion, they are responsible for component updates useState
, responsible for the implementation of side effects useEffect
, responsible for the preservation of data useRef
, responsible for caching optimization useMemo
, as for useCallback
, useReducer
, useLayoutEffect
principle and that four key hooks
compare They are similar, so I won’t explain them one by one.
Let's write a component first, and use the above four main hooks
:
Please remember the following code snippets, the following explanation will be expanded with the following code snippets
import React , { useEffect , useState , useRef , useMemo } from 'react'
function Index(){
const [ number , setNumber ] = useState(0)
const DivDemo = useMemo(() => <div> hello , i am useMemo </div>,[])
const curRef = useRef(null)
useEffect(()=>{
console.log(curRef.current)
},[])
return <div ref={ curRef } >
hello,world { number }
{ DivDemo }
<button onClick={() => setNumber(number+1) } >number++</button>
</div>
}
Next, let's study together what we wrote above the four hooks
will eventually become?
1 mountWorkInProgressHook
When component initialization, every hooks
execution, such as useState()
, useRef()
, will call mountWorkInProgressHook
, mountWorkInProgressHook
in the end do what to write, let us work together to analyze:
react-reconciler/src/ReactFiberHooks.js \-> mountWorkInProgressHook
function mountWorkInProgressHook() {
const hook: Hook = {
memoizedState: null, // useState中 保存 state信息 | useEffect 中 保存着 effect 对象 | useMemo 中 保存的是缓存的值和deps | useRef中保存的是ref 对象
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) { // 例子中的第一个`hooks`-> useState(0) 走的就是这样。
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
mountWorkInProgressHook
function does very simple things. First, each time a hooks
function is hook
object is generated, which stores the current hook
information, and then each hooks
is connected in a linked list and assigned to workInProgress
of memoizedState
. It also confirms the above-mentioned, the function component uses memoizedState
store the hooks
linked list.
As for what information is retained in the hook
Let me introduce them separately:
memoizedState : in useState save
state
information | useEffect
save the effect
objects | useMemo
stored value is cached and deps
| useRef
saved is ref
object.
baseQueue : usestate
and useReducer
save the latest update queue.
baseState : usestate
and useReducer
, in one update, the latest state
value generated.
queue : save the queue to be updated pendingQueue
, update function dispatch
and other information.
next : Point to the next hooks
object.
Then when our function component is executed, the four hooks
and workInProgress
will be in the relationship as shown in the figure.
shunxu.jpg
After knowing each hooks
relationship, we should understand why we can't declare hooks
conditional statement.
We use a picture to show what happens if we declare in a conditional statement.
If we put one of the above demo
, useRef
into the conditional statement,
let curRef = null
if(isFisrt){
curRef = useRef(null)
}
hoo11.jpg
because once declared in a conditional statement hooks
, the next component update function, hooks
chain structure, will be destroyed, current
tree memoizedState
cache hooks
information, and current workInProgress
inconsistency, if related to the read state
other operations, occurs abnormal.
The above describes what hooks
uses to prove uniqueness. The answer is through the order of the hooks
And why can’t you declare hooks
in the conditional statement. Next, we will follow the four directions to introduce what happened during initialization?
2 Initialize useState -> mountState
mountState
function mountState(
initialState
){
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// 如果 useState 第一个参数为函数,执行函数得到state
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null, // 带更新的
dispatch: null, // 负责更新函数
lastRenderedReducer: basicStateReducer, //用于得到最新的 state ,
lastRenderedState: initialState, // 最后一次得到的 state
});
const dispatch = (queue.dispatch = (dispatchAction.bind( // 负责更新的函数
null,
currentlyRenderingFiber,
queue,
)))
return [hook.memoizedState, dispatch];
}
mountState
done in the end, it will first get initialized state
, assign it to mountWorkInProgressHook
generated hook
object memoizedState
and baseState
property, and then create a queue
objects, which holds responsible for updating the information.
Let me talk about here, in the absence of the state assembly, useState
and useReducer
method of triggering function updates are dispatchAction
, useState
, can be seen as a simplified version of useReducer
, as for dispatchAction
how to update state
, updated components, we then studied down dispatchAction
.
Before we study must first figure out dispatchAction
what is?
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
)
const [ number , setNumber ] = useState(0)
dispatchAction
is setNumber
, dispatchAction
the first parameter and the second parameter, has been bind
to change currentlyRenderingFiber
and queue
, our incoming parameter is the third argument action
dispatchAction stateless component update mechanism
As the main function of the update, let’s take a look at it. I dispatchAction
, streamlined, streamlined,
function dispatchAction(fiber, queue, action) {
// 计算 expirationTime 过程略过。
/* 创建一个update */
const update= {
expirationTime,
suspenseConfig,
action,
eagerReducer: null,
eagerState: null,
next: null,
}
/* 把创建的update */
const pending = queue.pending;
if (pending === null) { // 证明第一次更新
update.next = update;
} else { // 不是第一次更新
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
const alternate = fiber.alternate;
/* 判断当前是否在渲染阶段 */
if ( fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber)) {
didScheduleRenderPhaseUpdate = true;
update.expirationTime = renderExpirationTime;
currentlyRenderingFiber.expirationTime = renderExpirationTime;
} else { /* 当前函数组件对应fiber没有处于调和渲染阶段 ,那么获取最新state , 执行更新 */
if (fiber.expirationTime === NoWork && (alternate === null || alternate.expirationTime === NoWork)) {
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
const currentState = queue.lastRenderedState; /* 上一次的state */
const eagerState = lastRenderedReducer(currentState, action); /**/
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
return
}
}
}
}
scheduleUpdateOnFiber(fiber, expirationTime);
}
}
Whether component class calls setState
, components or functions dispatchAction
, will have a update
objects, which records information on the update, then this update
placed to be updated pending
queue, dispatchAction
second step is to determine the current function component Whether the fiber
object is in the rendering stage, if it is in the rendering stage, then we do not need to update the current function components, just update the current update
expirationTime
.
If the current fiber
not in the update phase. Then get the latest state
by calling lastRenderedReducer
, and compare it with the last currentState
. If they are equal, then exit. This confirms why useState
, when the two values are equal, the component does not render. This mechanism and Component
in mode setState
is quite different.
If the two times state
not equal, then call scheduleUpdateOnFiber
schedule rendering current fiber
, scheduleUpdateOnFiber
is the main function react
We initialization mountState
* and * stateless component update mechanism . Next, let’s look at other 1608e782ad5462 hooks What are the operations done by the 1608e782ad5463 initialization?
3 Initialize useEffect -> mountEffect
The above mentioned the fiber
object memoizedState
stateless component to save the linked list formed by the hooks
So what information is stored in updateQueue
? We will find the answer in the process of useEffect
When we call useEffect
mountEffect
method will be called when the component is rendered for the first time. What exactly does this method do?
mountEffect
function mountEffect(
create,
deps,
) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = pushEffect(
HookHasEffect | hookEffectTag,
create, // useEffect 第一次参数,就是副作用函数
undefined,
nextDeps, // useEffect 第二次参数,deps
);
}
Each hooks
initialization creates a hook
object, and then hook the memoizedState
save the current effect hook
information.
There are two memoizedState
Don’t confuse everyone, I will remind you again
workInProgress / current
treememoizedState
contains the current function of each componenthooks
list form.- Each
hooks
onmemoizedState
save the currenthooks
information, different kinds ofhooks
ofmemoizedState
different content. The above method finally executed apushEffect
, let's take a look at whatpushEffect
did?
pushEffect creates an effect object and mounts updateQueue
function pushEffect(tag, create, destroy, deps) {
const effect = {
tag,
create,
destroy,
deps,
next: null,
};
let componentUpdateQueue = currentlyRenderingFiber.updateQueue
if (componentUpdateQueue === null) { // 如果是第一个 useEffect
componentUpdateQueue = { lastEffect: null }
currentlyRenderingFiber.updateQueue = componentUpdateQueue
componentUpdateQueue.lastEffect = effect.next = effect;
} else { // 存在多个effect
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
This paragraph is actually very simple. First, create a effect
, and determine if the component is rendered for the first time, then create componentUpdateQueue
, which is workInProgress
of updateQueue
. Then put effect
into updateQueue
.
Suppose we write this in a functional component:
useEffect(()=>{
console.log(1)
},[ props.a ])
useEffect(()=>{
console.log(2)
},[])
useEffect(()=>{
console.log(3)
},[])
Finally workInProgress.updateQueue
will be saved in this form:
7B8889E7-05B3-4BC4-870A-0D4C1CDF6981.jpg
Extension: effectList
effect list
can be understood as a effectTag
side effects list. It is a singly linked list structure composed of the fiber
node and the pointer nextEffect
, which also includes the first node firstEffect
, and the last node lastEffect
. React
uses a depth-first search algorithm. When traversing the fiber
render
fiber
with side effects is filtered out, and finally a effect list
linked list with only side effects is constructed. In commit
stage, React
get effect list
the data, by traversing effect list
, and according to each effect
node effectTag
type, performing each effect
, so that the corresponding DOM
execution tree changes.
4 Initialize useMemo -> mountMemo
I don’t know if you useMemo
is too complicated. Compared to other useState
, useEffect
etc., its logic is actually very simple.
function mountMemo(nextCreate,deps){
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
Initializing useMemo
is to create a hook
, and then execute useMemo
to get the value that needs to be cached, then deps
, and assign it to the current hook
memoizedState
. There is no complicated logic overall.
5 Initialize useRef -> mountRef
For useRef
initialization processing, it seems to be even simpler, let's take a look together:
function mountRef(initialValue) {
const hook = mountWorkInProgressHook();
const ref = {current: initialValue};
hook.memoizedState = ref;
return ref;
}
mountRef
initialization is simply to create a ref objects, object current
property to hold the value of initialization, and finally memoizedState
save ref
, to complete the operation.
6 Summary of mounted stage hooks
We summarize the initialization phase, react-hooks
do things in a first rendering component function execution context process, each react-hooks
execution, will have a hook
object and form a linked list structure, bound workInProgress
of memoizedState
on the property, Then the state on react-hooks
is bound to the memoizedState
attribute of the hooks
For effect
side-effect hooks, they will be bound to workInProgress.updateQueue
, wait until the commit
stage, dom
tree construction is completed, and execute each effect
side-effect hook.
Four hooks update stage
The above introduces the first rendering function component, react-hooks
initialization does, then we analyze it,
For the update phase, it means that the workInProgress
tree has been assigned to the current
tree last time. memoizedState
, which stores hooks
information, already exists on the current
tree. The react
processing logic for hooks
is similar to the fiber
For a function component updates, when executed again hooks
time functions, such as useState(0)
, first from current
of hooks
found in the current workInProgressHook
, corresponding currentHooks
, then copy currentHooks
to workInProgressHook
, next hooks
time function execution, the The latest status is updated to workInProgressHook
to ensure that the hooks
status is not lost.
So every time the function component is updated, every time the react-hooks
function is executed, there needs to be a function to do the above operation, this function is updateWorkInProgressHook
, let's look at this updateWorkInProgressHook
together.
1 updateWorkInProgressHook
function updateWorkInProgressHook() {
let nextCurrentHook;
if (currentHook === null) { /* 如果 currentHook = null 证明它是第一个hooks */
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else { /* 不是第一个hooks,那么指向下一个 hooks */
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook
if (workInProgressHook === null) { //第一次执行hooks
// 这里应该注意一下,当函数组件更新也是调用 renderWithHooks ,memoizedState属性是置空的
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
/* 这个情况说明 renderWithHooks 执行 过程发生多次函数组件的执行 ,我们暂时先不考虑 */
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
invariant(
nextCurrentHook !== null,
'Rendered more hooks than during the previous render.',
);
currentHook = nextCurrentHook;
const newHook = { //创建一个新的hook
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) { // 如果是第一个hooks
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else { // 重新更新 hook
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
The logic of this paragraph is roughly like this:
- First, if it is the first time to execute the
hooks
function, then take outmemoizedState
current
tree, which is the oldhooks
. - Then declare variables
nextWorkInProgressHook
, there should be worth noting that, under normal circumstances, oncerenderWithHooks
execution,workInProgress
onmemoizedState
will be left blank,hooks
perform functions order,nextWorkInProgressHook
should have been asnull
, then under what circumstancesnextWorkInProgressHook
notnull
, that is, whenrenderWithHooks
execution of 0608e782ad6019, the function component was executed many times, which is the logicrenderWithHooks
if (workInProgress.expirationTime === renderExpirationTime) {
// ....这里的逻辑我们先放一放
}
The logic here is actually to determine that if the current function component is still in the rendering priority after the current function component is executed, it means that the function component has a new update task, then the function component is executed cyclically. This causes the above-mentioned situation where nextWorkInProgressHook
not null
- Finally, copy the
current
ofhooks
and assign it toworkInProgressHook
, which is used to update a new round ofhooks
status.
Next, let's take a look at the four types of hooks
. In a component update, we did those operations separately.
2 updateState
useState
function updateReducer(
reducer,
initialArg,
init,
){
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current = currentHook;
let baseQueue = current.baseQueue;
const pendingQueue = queue.pending;
if (pendingQueue !== null) {
// 这里省略... 第一步:将 pending queue 合并到 basequeue
}
if (baseQueue !== null) {
const first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
do {
const updateExpirationTime = update.expirationTime;
if (updateExpirationTime < renderExpirationTime) { //优先级不足
const clone = {
expirationTime: update.expirationTime,
...
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
} else { //此更新确实具有足够的优先级。
if (newBaseQueueLast !== null) {
const clone= {
expirationTime: Sync,
...
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
/* 得到新的 state */
newState = reducer(newState, action);
}
update = update.next;
} while (update !== null && update !== first);
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = newBaseQueueFirst;
}
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
const dispatch = queue.dispatch
return [hook.memoizedState, dispatch];
}
This paragraph looks very complicated, let us slowly understand it, first merge the last updated pending queue
into basequeue
, why do we want to do this, for example, we write this in the click event again,
function Index(){
const [ number ,setNumber ] = useState(0)
const handerClick = ()=>{
// setNumber(1)
// setNumber(2)
// setNumber(3)
setNumber(state=>state+1)
// 获取上次 state = 1
setNumber(state=>state+1)
// 获取上次 state = 2
setNumber(state=>state+1)
}
console.log(number) // 3
return <div>
<div>{ number }</div>
<button onClick={ ()=> handerClick() } >点击</button>
</div>
}
Click the button, print 3
Three setNumber
generated update
will for the time being into pending queue
, the next component function execution time, three times update
be merged into baseQueue
. The structure is as follows:
setState.jpg
Next will present useState
or useReduer
corresponding hooks
on baseState
and baseQueue
update to the latest state. Will cycle baseQueue
of update
, copy update
, update expirationTime
, for there is enough priority update
(above three setNumber
produced update
have sufficient priority), we want to get the latest state
state. , Will execute each useState
action
. Get the latest state
.
update state
sset1.jpg
There are two questions here🤔️:
- Question 1:
action
n’t it enough to execute the last 0608e782ad67c6?
Answer: The reason is simple. The logic of useReducer
similar to useState
If the first parameter is a function, it will reference the update
generated by the state
needs to be called cyclically, each update
of reducer
, if setNumber(2)
is the case of 0608e782ad6858, then only use the updated value, then setNumber(state=>state+1)
Enter the last state
get the latest state
.
- Question 2: Under what circumstances will there be insufficient priority (
updateExpirationTime < renderExpirationTime
)?
Answer: This situation usually occurs when we call setNumber
and call scheduleUpdateOnFiber
render the current component, and a new update is generated, so the final execution of reducer
update state
task is handed over to the next update.
3 updateEffect
function updateEffect(create, deps): void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy = undefined;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
pushEffect(hookEffectTag, create, destroy, nextDeps);
return;
}
}
}
currentlyRenderingFiber.effectTag |= fiberEffectTag
hook.memoizedState = pushEffect(
HookHasEffect | hookEffectTag,
create,
destroy,
nextDeps,
);
}
useEffect
do is very simple, judge twice deps
equal, if not need to perform this update instructions are equal, then a direct call pushEffect
, where attention effect
label, hookEffectTag
, if not equal, then update effect
, and assigned to hook.memoizedState
, here The label is HookHasEffect | hookEffectTag
, and then in the commit
stage, react
will use the label to determine whether to execute the current effect
function.
4 updateMemo
function updateMemo(
nextCreate,
deps,
) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps; // 新的 deps 值
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1]; // 之前保存的 deps 值
if (areHookInputsEqual(nextDeps, prevDeps)) { //判断两次 deps 值
return prevState[0];
}
}
}
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
In the component update process, we execute the useMemo
function. What we do is actually very simple. It is to determine whether the two deps
are equal. If you don’t want to wait and prove that the dependency changes, then execute useMemo
to get the new value Then re-assign the value to hook.memoizedState
. If the equality proves that there is no dependency change, then the cached value is directly obtained.
But here is one point. It is worth noting that nextCreate()
executed, if usestate
is referenced, the variable will be referenced and cannot be recycled by the garbage collection mechanism. This is the principle of closure. Then the accessed attribute may not be the latest value, so you need to The referenced value is added to the dependency dep
array. Every time dep
changed and re-executed, there will be no problems.
Warm tips: Many students said useMemo
, what scene is it used, and will it be counterproductive? Through the analysis of the source code principle, I can clearly say that it can basically be used with confidence. To put it bluntly, it can be customized. To change the cache, the stored value is just a value.
5 updateRef
function updateRef(initialValue){
const hook = updateWorkInProgressHook()
return hook.memoizedState
}
The function component updates useRef to do a simpler thing, that is, it returns the cached value, that is, no matter how the function component is executed, how many times it is executed, hook.memoizedState
memory points to an object, so it explains why useEffect
, useMemo
, useRef
not Dependency injection is needed to access the latest changed value.
One click event update
91A72028-3A38-4491-9375-0895F420B7CD.jpg
Five summary
Above, we have function component to function component update rendering , two-dimensional decomposition explained the react-hooks
, mastered the react-hooks
and internal operating mechanism, which will help us in our work, and better use react-hooks
.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。