react-hooks源码解析
参考资料
掘金用户:🍼holyZhengs
导语
hooks是function
组件渲染中的一个环节。作用是将一部分组件的数据处理逻辑独立出来,以渐进式方式的加入到函数组件中。
源码地址packages/react-reconciler/src/ReactFiberHooks.js
根据源码实现简易hookDemo的代码地址:
src/hookDemo
目录下的CommonDemo
组件
来玩局游戏吧!
废话不多说,上图:
- 整个hooks执行阶段,可以类比成开一局游戏。
- 准备阶段(投币开始):先重置游戏机(
resetHooks
)->投币(renderWithHooks
)->创建游戏环境。- 执行阶段(游戏通关):开始游戏(
Component
)->按顺序通关(hooks
)->通关成功输出战绩(虚拟dom节点
)- hooks函数分为
mount
和update
两种模式。准备阶段时会判断fiber
是否初次使用hooks
来决定使用何种状态。
一、关卡(useState,useReducer)
useState需要解决的问题
- 作为一个公共函数,如何保证多个调用之间状态互不干扰?
- 以fiber为维度,每一个fiber保存一个hook链表(
fiber.memoizedState
)。以function中的执行顺序为依据,从保存的hook链表中获取状态 - useState执行于function组件中,而function组件在fiber树遍历时被调用。在function调用之前,react会先标记当前工作fiber。
- 每一个fiber有自己的hook链表,因为内部的hook钩子执行顺序不会被改变。hook的执行顺序将与fiber保存的hook链表顺序一一对应。以先进先出的方式执行
- 所以每一个hook钩子函数的执行,都能够正确的拿到对应的缓存状态,而不会相互干扰
- 以fiber为维度,每一个fiber保存一个hook链表(
// 创建hook并加入链表
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
queue: null,
baseUpdate: null,
next: null,
};
if (workInProgressHook === null) {
firstWorkInProgressHook = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
// 更新组件时获取hook,可以用于更新阶段,也可以用于创建阶段
function updateWorkInProgressHook(): Hook {
// 因为根据链表顺序获取hook,每执行一个钩子之前,先将下一个hook设置为活跃状态并返回
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
nextCurrentHook = currentHook !== null ? currentHook.next : null;
} else {
// 如果没有匹配到对应的hook,则创建一个并加入到队列
invariant(
nextCurrentHook !== null,
'Rendered more hooks than during the previous render.',
);
currentHook = nextCurrentHook;
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
queue: currentHook.queue,
baseUpdate: currentHook.baseUpdate,
next: null,
};
if (workInProgressHook === null) {
workInProgressHook = firstWorkInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
nextCurrentHook = currentHook.next;
}
return workInProgressHook;
}
- 多个useState如何保存?
按执行顺序保存,多个钩子函数互不影响
- useState如何触发视图层更新,更新逻辑是怎样的?
dispatchAction.bind(null, fiber, queue)
执行时,会提交当前节点的变更。- setState方法执行时,会标记当前节点的更新状态,并触发根节点更新
const [state, setState] = useState(null)
做了什么?
const [state, setState] = useState(null)
运行时可分为mount和update两个阶段。这两个阶段在源码中对应着以下两个不同的useState方法。
mount阶段
- 创建一个钩子,并创建queue队列(缓存状态变更的队列)
- 变更方法绑定组件的fiber节点与queue队列,以关联组件
- 将初始化状态与变更方法返回给组件
- 源码分析:
function mountState<S>(
initialState: (() => S) | S
): [S, Dispatch<BasicStateAction<S>>] {
// 从钩子链表中获取当前执行的钩子
const hook = mountWorkInProgressHook();
// 获取传入的初始化参数
if (typeof initialState === 'function') {
initialState = initialState();
}
// 将历史状态与当前变更状态初始化
hook.memoizedState = hook.baseState = initialState;
// 创建储存状态变更的链表
const queue = (hook.queue = {
last: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
// 创建一个修改方法,并绑定到当前钩子。
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
// Flow doesn't know this is non-null, but we do.
((currentlyRenderingFiber: any): Fiber),
queue,
): any));
return [hook.memoizedState, dispatch];
}