hooks 内部结构
最近在对 React 进行一些深入化的学习,把困扰很久的疑惑 查资料弄清楚啦!
疑惑
const [age, setAge] = useState(initialAge);
const [name, setName] = useState(initialName);
1. 当使用这两个 状态的时候,hooks 是怎么区分这两个状态的呢?
2. hooks是如何在每次重新渲染之后都能返回最新的状态?
React内部是怎么区分这两个状态的呢? Function Component 不像 Class Component那样可以将私有状态挂载到类实例中并通过对应的key来指向对应的状态,而且每次的页面的刷新或者说组件的重新渲染都会使得 Function 重新执行一遍。所以React中必定有一种机制来区分这些Hooks。
React是如何在每次重新渲染之后都能返回最新的状态? Class Component因为自身的特点可以将私有状态持久化的挂载到类实例上,每时每刻保存的都是最新的值。而 Function Component 由于本质就是一个函数,并且每次渲染都会重新执行。所以React必定拥有某种机制去记住每一次的更新操作,并最终得出最新的值返回。比如这些状态究竟存放在哪?为什么只能在函数顶层使用Hooks而不能在条件语句等里面使用Hooks?
答案
// react-reconciler/src/ReactFiberHooks.js
export type Hook = {
memoizedState: any,
baseState: any,
baseUpdate: Update<any, any> | null,
queue: UpdateQueue<any, any> | null,
next: Hook | null, // 指向下一个Hook
};
从Hook的类型定义中就可以看到,React 对Hooks的定义是链表。也就是说我们组件里使用到的Hooks是通过链表来联系的,上一个Hooks的next指向下一个Hooks。
在mount阶段,就会生产如下图这样的单链表:
如何返回最新的值?
useState和useReducer都是使用了一个queue链表来存放每一次的更新。以便后面的update阶段可以返回最新的状态。每次我们调用dispatchAction方法的时候,就会形成一个新的updata对象,添加到queue链表上,而且这个是一个循环链表。
可以看一下 dispatchAction 方法的实现:
// react-reconciler/src/ReactFiberHooks.js
// 去除特殊情况和与fiber相关的逻辑
function dispatchAction(fiber,queue,action,) {
const update = {
action,
next: null,
};
// 将update对象添加到循环链表中
const last = queue.last;
if (last === null) {
// 链表为空,将当前更新作为第一个,并保持循环
update.next = update;
} else {
const first = last.next;
if (first !== null) {
// 在最新的update对象后面插入新的update对象
update.next = first;
}
last.next = update;
}
// 将表头保持在最新的update对象上
queue.last = update;
// 进行调度工作
scheduleWork();
}
也就是我们每次执行dispatchAction方法,比如setAge或setName。就会创建一个保存着此次更新信息的update对象,添加到更新链表queue上。然后每个Hooks节点就会有自己的一个queque。比如假设我们执行了下面几个语句:
setAge(19);
setAge(20);
setAge(21);
那么我们的Hooks链表就会变成这样:
在Hooks节点上面,会如上图那样,通过链表来存放所有的历史更新操作。以便在update阶段可以通过这些更新获取到最新的值返回给我们。这就是在第一次调用useState或useReducer之后,每次更新都能返回最新值的原因。
在update阶段,也就是我们组件第二次第三次执行到useState或useReducer的时候,会遍历update对象循环链表,执行每一次更新去计算出最新的状态来返回,以保证我们每次刷新组件都能拿到当前最新的状态。
总结
看到这里我们在回头看看最初的一些疑问:
- React 如何管理区分Hooks?
React通过单链表来管理Hooks
按Hooks的执行顺序依次将Hook节点添加到链表中
- useState和useReducer如何在每次渲染时,返回最新的值?
每个Hook节点通过循环链表记住所有的更新操作
在update阶段会依次执行update循环链表中的所有更新操作,最终拿到最新的state返回
- 为什么不能在条件语句等中使用Hooks?
比如如图所示,我们在mount阶段调用了useState(‘A’), useState(‘B’), useState(‘C’),如果我们将useState(‘B’) 放在条件语句内执行,并且在update阶段中因为不满足条件而没有执行的话,那么没法正确的重Hooks链表中获取信息。React也会给我们报错。
Hooks链表放在哪?
组件构建的Hooks链表会挂载到FiberNode节点的memoizedState上面去。
参考文章:
这一篇写的特别好
https://juejin.cn/post/6844904080758800392