从源码的角度告诉你 《为啥2次传入setState的值相同,函数组件不更新?》
大家好,我是小九九的爸爸,今天我们来分析一道面试题:《两次传入useState的值相同,函数组件为啥不更新?》。大家准备好了嘛,我要发车了。
一、现象描述
代码如下:
function FC(){
// 这里需要做出一些定义,我们将使用 `setHook` 来代替useState返回的数组里的第二个参数
// 在本例中,setHook就是 setState0、setState1的代名词
let [state0, setState0] = useState(0);
let [state1, setState1] = useState(1);
const clickButton = () => {
setState0((value) => {
return 0;
});
}
return <div>
内容:{state0}
<div>
随机数:{Math.random()*100}
</div>
<button onClick={clickButton}>+1</button>
</div>
}
上述代码对应的现象是下面这样:
二、你将学到的知识
1、函数组件内部是如何维护状态的?
2、函数组件内部是如何判断是否要更新的?
3、useState的返回值为什么是数组?
三、useState源码分析
函数组件是如何通过useState来保存状态的?
先直接告诉结论吧:一个组件内部如果多次调用 useState,那么就会在组件内部 依次创建相应层次来保存state对象,这些层次通过next属性连接成一条链
。
useState初始化时的源码分析
第一次调用useState
第一步:创建state对象
函数式组件通过调用 mountWorkInProgressHook()
来创建state对象,其源码如下:
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
在上述代码中,currentlyRenderingFiber
代表当前正在渲染的组件对象,当我们第一次渲染<FC/>
组件,并且代码执行到 useState(0)
时,mountWorkInProgressHook
里的代码是这样的:
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
}
return workInProgressHook;
}
此时 currentlyRenderingFiber
对象是这样:
/**
currentlyRenderingFiber: {
memoizedState: {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
}
}
*/
很好,目前我们已经知道了state对象是如何创建的了,那么我们接着看一下 mountState
剩余的逻辑。
第二步:保存initState
这部分代码比较直观,请直接看代码里的注释即可。
function mountState(initialState) {
// 1、创建state变量 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
var hook = mountWorkInProgressHook();
/**
执行完上一步后,各变量的watch情况:
hook: { currentlyRenderingFiber:{
memoizedState: null, memoizedState:{
baseState: null, memoizedState:null,
baseQueue: null, baseState:null,
queue: null, baseQueue:null,
next: null queue:null,
} next:null
}
}
*/
// 2、保存initState、以及各变量赋值 ++++++++++++++++++++++++++++++++++++++++++++
hook.memoizedState = hook.baseState = initialState;
/**
执行完上一步后,各变量的watch情况:
hook:{ currentlyRenderingFiber:{
memoizedState: 0, memoizedState: {
baseState: 0, memoizedState: 0,
baseQueue: null, baseState: 0,
queue: null, baseQueue: null,
next: null queue: null,
} next: null
}
}
*/
const queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: 0,
};
hook.queue = queue;
/**
执行完上一步后,各变量的watch情况:
hook:{ currentlyRenderingFiber:{
memoizedState: 0, memoizedState:{
baseState: 0, memoizedState: 0,
baseQueue: null, baseState: 0,
queue: { baseQueue: null,
pending: null, queue: {
interleaved: null, pending: null,
lanes: NoLanes, interleaved: null,
dispatch: null, lanes: NoLanes,
lastRenderedReducer: basicStateReducer, dispatch: null,
lastRenderedState: 0, lastRenderedReducer: basicStateReducer,
lastRenderedState: 0
}, },
next: null next: null
} }
}
*/
// 3、将state与action绑定 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
const dispatch = (queue.dispatch = (dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
)));
return [hook.memoizedState, dispatch];
}
第三步:将state与action绑定
这里我们需要提前做一下setHook的定义,
这一步主要是通过调用 dispatchSetState
函数来完成,这一部分主要代码如下:
/**
dispatchSetState函数形参里的action的值取决于setHook里的参数,比如:
let [count, setCount] = useState(0);
setCount(1) --> action就是1
setCount( () => 1 ) --> action就是 () => 1 这个函数
*/
function dispatchSetState( fiber, queue, action ){
// 省略其余代码 =================
const update = {
lane,
action,
hasEagerState: false,
eagerState: null,
next: null,
};
var currentState = queue.lastRenderedState;
var eagerState = lastRenderedReducer(currentState, action);
if (Object.is(eagerState, currentState)) {
return;
}
// 省略其余代码 ===============
}
结合上面的源代码,我们发现setHook
就是dispatch
,dispatch
就是dispatchSetState
。在这个函数里,我们会拿到组件内部的当前状态(currentState)
、即将要改变的下一个状态(eagerState)
来做Object.is浅比较
,如果2个状态相等,就直接返回,任何事情都不会做,这也是为啥多次setState的值相同,但是组件不更新的原因。
第四步:为什么useState返回的是一个数组?
这个问题也是面试中会被考的一个点。针对这个点,我们首先是想,可以返回什么样的数据结构?
根据目前的框架使用情况来看的话,似乎返回格式
只能是 数组或者对象
。
那么 数组解构
相比 对象解构
有哪些优势呢?
我觉着 变量名自定义
这是 数组解构
能够取胜的绝对原因。这毕竟降低了用户的学习成本。增强使用者代码的可读性。
第二次调用useState
还记得我们的 <FC/>组件
的代码嘛,里面调用了2次useState
function Fc(){
let [state0, setState0] = useState(0);
let [state1, setState1] = useState(1);
...
}
当我们第一次调用useState(0)
的时候,组件内部的state状态是这样维护的:
/**
currentlyRenderingFiber: {
memoizedState: {
memoizedState: 0,
baseState: 0,
baseQueue: null,
queue: {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: 0
},
next: null
}
}
*/
当我们第二次调用 useState(1)
的时候,实际上就是重新 走了一遍第一次调用useState(0)
,唯一的区别就是创建state对象的过程中
稍微不一样而已,我们现在一起来看下:
function mountWorkInProgressHook(){
const hook= {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// 组件内部第一次执行useState(0)的时候会走这里
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 组件内部第二次执行useState(1)的时候会走这里
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
workInProgressHook = workInProgressHook.next = hook;
这行代码可了不得,做了2件事,我们先把这行代码拆解一下:
workInProgressHook.next = hook;
workInProgressHook = hook;
2件事如下:
- 将后续调用useState时创建的state对象通过next属性连成链。
- workInProgressHook始终指向最新的state对象。
第二次调用useState(1)后,此时 currentlyRenderingFiber
与 workInProgressHook
的value情况如下:
/**
currentlyRenderingFiber:{
memoizedState: {
memoizedState: 0,
baseState: 0,
baseQueue: null,
queue: {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState:0
}
next: {
memoizedState: 1,
baseState: 1,
baseQueue: null,
queue: {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: 1
},
next: null
}
}
}
workInProgressHook: {
memoizedState: 1,
baseState: 1,
baseQueue: null,
queue: {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: 1
},
next: null
}
*/
四、最后
好啦,本篇文章到这里就结束啦,如果在上述过程中发现任何问题,欢迎评论区里指正。同时,如果您觉得还不错,还请给一个小赞赞~~
转载自:https://juejin.cn/post/7240297635427663930