状态总要持久化存储才行,不然前端就混不下去了,只能去炒粉了。我们都知道React 处理 class 组件的方式是将 class 实例化,所以 class 组件将方法、状态、钩子作为成员属性直接使用就可以了。
而 React 在处理函数组件时是将其当作函数调用的,没有实例可言,自然也不能将「状态」或是「副作用」挂在原型上。所以只能将 Hook 放在一个和函数组件有关联的地方,即函数组件在渲染过程中对应的 Fiber Node。函数组件在渲染时将 Hook 按照调用顺序以链表的形式挂在 Fiber Node 上面。得到的链表如下所示,其中 Fiber Node 的属性 memoizedState 用来指向Hook 链表的第一个节点,Hook 的属性 memoizedState 用来存放 state 值或 effect回调函数。
挂载 Hook 的伪代码如下所示:
function renderWithHooks(current) {
//current为当前正在处理的Fiber Node //nextCurrentHook先指向第一个Hook nextCurrentHook = current !== null ? current.memoizedState : null
//。。。省略其他代码
//执行函数组件 let children = Component(props, refOrContext);
//将指向链表第一个节点的firstWorkInProgressHook //赋值给currentlyRenderingFiber.memoizedState const renderedWork: Fiber = (currentlyRenderingFiber: any);
//将Hook挂在FiberNode.memoizedState下 renderedWork.memoizedState = firstWorkInProgressHook;
renderedWork.expirationTime = remainingExpirationTime;
//将Effect Hook挂在FiberNode.updateQueue下 renderedWork.updateQueue = (componentUpdateQueue: any);
renderedWork.effectTag |= sideEffectTag;
//重置Hook指针的指向,使每一个FiberNode都有一个专属于自身的、独立的链表 currentHook = null;
nextCurrentHook = null;
firstWorkInProgressHook = null;
workInProgressHook = null;
nextWorkInProgressHook = null;}function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
baseState: null,
queue: null,
baseUpdate: null,
next: null,
};
if (workInProgressHook === null) {
//如果当前Hook 链表为空则证明是第一次调用Hook //需将当前调用Hook作为链表中的第一个节点 firstWorkInProgressHook = workInProgressHook = hook;
} else {
// 其余Hook均按顺序挂在上一个Hook的next下,形成一个链表结构 workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;}function updateWorkInProgressHook() {
//在每次渲染时,nextCurrentHook最开始都会指向第一个Hook currentHook = nextCurrentHook;
const newHook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
queue: currentHook.queue,
baseUpdate: currentHook.baseUpdate,
next: null,
};
//将Hook从原链表中取出,放进新的链表中 if (workInProgressHook === null) {
workInProgressHook = firstWorkInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
//指向下一个Hook nextCurrentHook = currentHook.next;
return workInProgressHook;}
每个组件的 fiber 上都有个 memorizedState 属性用于存储这个组件的所有 hooks。hooks 中的每个 hook 也有个 memorizedState 用于存储这个 hook 的数据。而每个 hook 还有个 next 指向下一个 hook。
这里我们用链表实现 hooks 的存储,也可以用数组实现,只要有顺序就行。