前言
本文的React代码版本为18.2.0
可调试的代码仓库为:GitHub - yyyao-hh/react-debug at master-pure7
自React16.8引入Hook以来,它彻底改变了我们编写React组件的方式。Hook不仅让函数组件具备了类组件的能力,还带来了更优雅的代码组织和逻辑复用方式。本文将深入React18源码,全面解析Hook的实现机制,揭示其背后的设计思想和实现细节。
Hook 的定义
首先看几个常用hooks的定义。可以看出useState、useEffect都是通过dispatcher对象去调用对应的方法
useState:dispatcher.useStateuseEffect:dispatcher.useEffect...
/* react/packages/react/src/ReactHooks.js */
export function useState(...) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
export function useEffect(...) {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}
然后我们找到初始化定义的地方,dispatcher是通过ReactCurrentDispatcher.current取值的
/* react/packages/react/src/ReactCurrentDispatcher.js */
const ReactCurrentDispatcher = {
current: (null: null | Dispatcher)
};
/* react/packages/react/src/ReactHooks.js */
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return ((dispatcher: any): Dispatcher);
}
那对于ReactCurrentDispatcher.current的赋值操作,我们得追溯到renderWithHooks函数(在构建Fiber树一文时有提到)。在渲染一个函数组件时,在调用组件函数本身之前,会先设置好正确的Dispatcher。
依旧通过current === null来判断当前是初始化(HooksDispatcherOnMount)还是更新(HooksDispatcherOnUpdate),其中包含了各种hooks的操作
/* react/packages/react-reconciler/src/ReactFiberHooks.old.js */
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// 调用函数组件
let children = Component(props, secondArg);
return children;
};
// 1. 初始化(Mount)
const HooksDispatcherOnMount: Dispatcher = {
useEffect: mountEffect,
useRef: mountRef,
useState: mountState,
...
};
// 2. 更新(Update)
const HooksDispatcherOnUpdate: Dispatcher = {
useEffect: updateEffect,
useRef: updateRef,
useState: updateState,
...
};
初始化时的 Hook
观察HooksDispatcherOnMount中的每一个处理函数,我们发现每个函数中都会调用mountWorkInProgressHook 函数
// react/packages/react-reconciler/src/ReactFiberHooks.old.js
const HooksDispatcherOnMount: Dispatcher = {
useEffect: mountEffect,
useRef: mountRef,
useState: mountState
};
// useEffect
function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
const hook = mountWorkInProgressHook();
...
};
function mountEffect(...) {
return mountEffectImpl(...);
};
// useRef
function mountRef(...) {
const hook = mountWorkInProgressHook();
...
};
// useState
function mountState(...) {
const hook = mountWorkInProgressHook();
...
};
我们紧接着观察这个函数,
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null, // 用于存储当前的状态值
baseState: null, // 基础状态: 用于计算新的状态
baseQueue: null, // 基础队列: 存储被跳过的更新(并发模式下)
queue: null, // 更新队列: 存储所有要处理的状态更新 (最重要!)
next: null, // 指向下一个Hook的指针 (用于构成链表)
};
// 保存hook到链表上
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
更新时的 Hook
观察HooksDispatcherOnUpdate中的每一个处理函数,我们发现每个函数中都会调用updateWorkInProgressHook函数
// react/packages/react-reconciler/src/ReactFiberHooks.old.js
const HooksDispatcherOnUpdate: Dispatcher = {
useEffect: updateEffect,
useRef: updateRef,
useState: updateState
};
// updateEffect
function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
const hook = updateWorkInProgressHook();
...
};
function updateEffect(...) {
return updateEffectImpl(...);
};
// updateRef
function updateRef(...) {
const hook = updateWorkInProgressHook();
...
};
// updateState
function updateReducer(...) {
const hook = updateWorkInProgressHook();
...
};
function updateState(...) {
return updateReducer(basicStateReducer, (initialState: any));
};
我们紧接着观察这个函数,
function updateWorkInProgressHook() {
// 获取 current 树上的 Fiber 的 hook 链表
let nextCurrentHook: null | Hook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
// There's already a work-in-progress. Reuse it.
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
// Clone from the current hook.
if (nextCurrentHook === null) {
throw new Error('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) {
// This is the first hook in the list.
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
// Append to the end of the list.
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
总结
React Hook的实现展示了几个重要的软件设计原则:
- 链表数据结构:通过简单的链表结构高效管理组件状态
- 闭包的应用:利用闭包捕获状态和更新函数
- 函数式编程思想:纯函数与副作用分离
- 优先级调度:在并发模式下实现高性能更新
Hook的设计不仅解决了React开发中的实际问题,也为未来的React发展奠定了基础。理解Hook的实现原理,有助于我们编写更高效、更可靠的React应用。
下一章我们将详细的学习某个具体hook的实现原理