Appearance
第7章 Hooks实现原理
本章将深入React Hooks的实现原理,理解Hooks的数据结构、Dispatcher机制、以及useState、useEffect等核心Hooks的源码实现。这是理解React函数组件状态管理的关键。
在React 16.8之前,函数组件是无状态的,只能用于展示UI。如果需要状态管理或生命周期方法,必须使用类组件。React 16.8引入了Hooks,彻底改变了这一局面。Hooks让函数组件拥有了状态、副作用、上下文等能力,使得函数组件成为React开发的主流方式。
为什么React要引入Hooks?Hooks是如何在函数组件中保存状态的?为什么Hooks必须在函数组件顶层调用?useState和useEffect是如何工作的?并发Hooks如何实现非阻塞更新?
本章将逐一解答这些问题,带你深入理解Hooks的设计与实现。
7.1 Hooks的数据结构
Hooks的核心是一个链表结构,每个Hook节点保存了该Hook的状态和更新队列。
7.1.1 Hook对象
每个Hook都对应一个Hook对象,定义在ReactFiberHooks.js中:
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:194-200(React 19.3.0)
export type Hook = {
memoizedState: any,
baseState: any,
baseQueue: Update<any, any> | null,
queue: any,
next: Hook | null,
};Hook对象的属性
memoizedState:当前Hook的状态
- 对于useState:保存state值
- 对于useEffect:保存effect对象
- 对于useMemo:保存缓存的值和依赖数组
- 对于useRef:保存ref对象
baseState:基础状态
- 用于计算最终state的起点
- 在有优先级跳过的更新时使用
baseQueue:基础更新队列
- 保存被跳过的低优先级更新
- 在下次渲染时会重新应用
queue:更新队列
- 对于useState:保存dispatch创建的更新
- 对于useEffect:保存effect的依赖数组
next:指向下一个Hook
- 形成Hook链表
- 保证Hooks的调用顺序
7.1.2 Effect对象
useEffect、useLayoutEffect等副作用Hook使用Effect对象:
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:217-233(React 19.3.0)
type EffectInstance = {
destroy: void | (() => void),
};
export type Effect = {
tag: HookFlags,
inst: EffectInstance,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
next: Effect,
};Effect对象的属性
tag:Effect的类型标记
- HasEffect:需要执行
- Passive:useEffect(异步执行)
- Layout:useLayoutEffect(同步执行)
- Insertion:useInsertionEffect(在DOM变更前执行)
inst:Effect实例
- destroy:清理函数
- 在effect重新执行前调用
create:effect函数
- 用户传入的副作用函数
- 返回值是清理函数
deps:依赖数组
- 用于判断是否需要重新执行effect
- null表示每次都执行
next:指向下一个Effect
- 形成Effect环形链表
- 保证effect的执行顺序
7.1.3 Hooks链表
Hooks在Fiber节点上以链表形式存储:
javascript
// Fiber节点的memoizedState指向第一个Hook
fiber.memoizedState = firstHook;
// Hook链表结构
firstHook -> secondHook -> thirdHook -> nullHooks链表的特点
- 单向链表:每个Hook通过next指针连接
- 顺序固定:Hooks的调用顺序必须保持一致
- 存储在Fiber:通过fiber.memoizedState访问
为什么Hooks必须在顶层调用?
Hooks依赖调用顺序来匹配状态。如果Hooks在条件语句或循环中调用,顺序可能会变化,导致状态错乱。
jsx
// ✗ 错误:Hooks在条件语句中
function BadComponent({ condition }) {
if (condition) {
const [state, setState] = useState(0); // 顺序不固定
}
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
// ✓ 正确:Hooks在顶层
function GoodComponent({ condition }) {
const [state, setState] = useState(0); // 顺序固定
const [count, setCount] = useState(0);
if (condition) {
// 使用state
}
return <div>{count}</div>;
}Hooks链表的构建过程
首次渲染:
1. 调用useState(0)
- 创建Hook1,memoizedState = 0
- fiber.memoizedState = Hook1
2. 调用useState(1)
- 创建Hook2,memoizedState = 1
- Hook1.next = Hook2
3. 调用useEffect(...)
- 创建Hook3,memoizedState = Effect对象
- Hook2.next = Hook3
结果:
fiber.memoizedState -> Hook1 -> Hook2 -> Hook3 -> null
更新时:
1. 调用useState(0)
- 复用Hook1,读取memoizedState
2. 调用useState(1)
- 复用Hook2,读取memoizedState
3. 调用useEffect(...)
- 复用Hook3,比较deps决定是否执行7.1.4 示例:Hooks链表图解
让我们通过一个完整的示例来理解Hooks链表的结构:
jsx
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('React');
const prevCount = useRef(0);
useEffect(() => {
prevCount.current = count;
}, [count]);
useEffect(() => {
document.title = `${name}: ${count}`;
}, [name, count]);
return (
<div>
<p>{name}: {count}</p>
<p>Previous: {prevCount.current}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}Hooks链表结构
详细结构
javascript
// Fiber节点
fiber = {
memoizedState: Hook1, // 指向第一个Hook
updateQueue: {
lastEffect: Effect2, // Effect环形链表
},
// ... 其他属性
};
// Hook1: useState(0)
Hook1 = {
memoizedState: 0,
baseState: 0,
baseQueue: null,
queue: {
pending: null,
dispatch: setCount,
},
next: Hook2,
};
// Hook2: useState('React')
Hook2 = {
memoizedState: 'React',
baseState: 'React',
baseQueue: null,
queue: {
pending: null,
dispatch: setName,
},
next: Hook3,
};
// Hook3: useRef(0)
Hook3 = {
memoizedState: { current: 0 },
baseState: null,
baseQueue: null,
queue: null,
next: Hook4,
};
// Hook4: useEffect(...)
Hook4 = {
memoizedState: {
tag: Passive | HasEffect,
inst: { destroy: undefined },
create: () => { prevCount.current = count; },
deps: [0],
next: Effect2,
},
baseState: null,
baseQueue: null,
queue: null,
next: Hook5,
};
// Hook5: useEffect(...)
Hook5 = {
memoizedState: {
tag: Passive | HasEffect,
inst: { destroy: undefined },
create: () => { document.title = `${name}: ${count}`; },
deps: ['React', 0],
next: Effect1, // 环形链表,指回第一个Effect
},
baseState: null,
baseQueue: null,
queue: null,
next: null,
};Effect环形链表
Effect1 -> Effect2 -> Effect1 (环形)
↑ ↓
└────────────────────┘
fiber.updateQueue.lastEffect = Effect2这种环形链表设计使得React可以从任意Effect开始遍历整个链表。
7.2 Dispatcher机制
Dispatcher是React Hooks的核心机制,它决定了每个Hook调用时应该执行哪个实现函数。
7.2.1 什么是Dispatcher
Dispatcher是一个对象,包含了所有Hook的实现函数。React根据组件的渲染阶段,切换不同的Dispatcher。
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:3870-3897(React 19.3.0)
export const ContextOnlyDispatcher: Dispatcher = {
readContext,
use,
useCallback: throwInvalidHookError,
useContext: throwInvalidHookError,
useEffect: throwInvalidHookError,
useImperativeHandle: throwInvalidHookError,
useLayoutEffect: throwInvalidHookError,
useInsertionEffect: throwInvalidHookError,
useMemo: throwInvalidHookError,
useReducer: throwInvalidHookError,
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
useDeferredValue: throwInvalidHookError,
useTransition: throwInvalidHookError,
useSyncExternalStore: throwInvalidHookError,
useId: throwInvalidHookError,
// ... 其他Hooks
};为什么需要Dispatcher?
- 不同阶段不同行为:首次渲染和更新时,Hook的行为不同
- 错误检测:在非渲染阶段调用Hook会抛出错误
- 开发环境增强:开发环境可以添加额外的检查和警告
7.2.2 三种Dispatcher
React有三种主要的Dispatcher:
1. ContextOnlyDispatcher
在组件渲染之外使用,所有Hook都会抛出错误。
javascript
const ContextOnlyDispatcher: Dispatcher = {
useState: throwInvalidHookError,
useEffect: throwInvalidHookError,
// ... 其他Hooks都抛出错误
};
function throwInvalidHookError() {
throw new Error(
'Invalid hook call. Hooks can only be called inside of the body of a function component.'
);
}使用场景:
- 组件渲染完成后
- 事件处理函数中
- 异步回调中
jsx
// ✗ 错误:在事件处理函数中调用Hook
function BadComponent() {
const handleClick = () => {
const [state, setState] = useState(0); // 抛出错误
};
return <button onClick={handleClick}>Click</button>;
}2. HooksDispatcherOnMount
首次渲染时使用,创建新的Hook对象。
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:3898-3925(React 19.3.0)
const HooksDispatcherOnMount: Dispatcher = {
readContext,
use,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useInsertionEffect: mountInsertionEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useSyncExternalStore: mountSyncExternalStore,
useId: mountId,
// ... 其他Hooks
};特点:
- 创建新的Hook对象
- 初始化Hook的状态
- 将Hook添加到链表
3. HooksDispatcherOnUpdate
更新时使用,复用已有的Hook对象。
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:3926-3953(React 19.3.0)
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
use,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
useInsertionEffect: updateInsertionEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
// ... 其他Hooks
};特点:
- 复用已有的Hook对象
- 读取Hook的状态
- 处理更新队列
7.2.3 Dispatcher的切换
React在渲染函数组件时,会切换Dispatcher。
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:547-565(React 19.3.0)
function renderWithHooks(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
props: any,
secondArg: any,
nextRenderLanes: Lanes,
): any {
// 1. 设置当前渲染的Fiber
currentlyRenderingFiber = workInProgress;
// 2. 清空Hooks链表
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
// 3. 根据是否首次渲染,选择Dispatcher
ReactSharedInternals.H =
current === null || current.memoizedState === null
? HooksDispatcherOnMount // 首次渲染
: HooksDispatcherOnUpdate; // 更新
// 4. 执行函数组件
let children = Component(props, secondArg);
// 5. 渲染完成,切换到ContextOnlyDispatcher
ReactSharedInternals.H = ContextOnlyDispatcher;
return children;
}Dispatcher切换流程
示例:Dispatcher切换过程
jsx
function Counter() {
// 此时Dispatcher是HooksDispatcherOnMount或HooksDispatcherOnUpdate
const [count, setCount] = useState(0);
const handleClick = () => {
// 此时Dispatcher是ContextOnlyDispatcher
// 如果在这里调用useState,会抛出错误
setCount(count + 1); // 正确:调用dispatch函数,不是Hook
};
return <button onClick={handleClick}>{count}</button>;
}首次渲染的流程
1. renderWithHooks开始
2. 设置ReactSharedInternals.H = HooksDispatcherOnMount
3. 执行Counter()
4. 调用useState(0)
- 实际调用mountState(0)
- 创建Hook对象
- 返回[0, dispatch]
5. 执行完成
6. 设置ReactSharedInternals.H = ContextOnlyDispatcher
7. 返回children更新时的流程
1. renderWithHooks开始
2. 设置ReactSharedInternals.H = HooksDispatcherOnUpdate
3. 执行Counter()
4. 调用useState(0)
- 实际调用updateState(0)
- 复用Hook对象
- 处理更新队列
- 返回[newState, dispatch]
5. 执行完成
6. 设置ReactSharedInternals.H = ContextOnlyDispatcher
7. 返回children7.2.4 服务端Hooks的特殊处理
在服务端渲染(SSR)时,React使用不同的Hooks实现。
javascript
// 文件:packages/react-server/src/ReactFizzHooks.js
// 服务端的Hook对象更简单
type Hook = {
memoizedState: any,
queue: UpdateQueue<any> | null,
next: Hook | null,
};
// 服务端的Dispatcher
const HooksDispatcher: Dispatcher = {
readContext,
use,
useCallback: useCallback,
useContext: useContext,
useEffect: useEffect, // 服务端不执行
useImperativeHandle: useImperativeHandle, // 服务端不执行
useLayoutEffect: useLayoutEffect, // 服务端不执行
useInsertionEffect: useInsertionEffect, // 服务端不执行
useMemo: useMemo,
useReducer: useReducer,
useRef: useRef,
useState: useState,
// ... 其他Hooks
};服务端Hooks的特点
- useEffect不执行:服务端没有副作用
- useState返回初始值:服务端只渲染一次
- useRef返回固定对象:服务端不需要持久化引用
- useMemo总是计算:服务端没有缓存
示例:服务端useState实现
javascript
// 文件:packages/react-server/src/ReactFizzHooks.js
function useState<S>(initialState: (() => S) | S): [S, Dispatch<S>] {
// 服务端useState只返回初始值,dispatch是空函数
return useReducer(
basicStateReducer,
typeof initialState === 'function' ? initialState() : initialState,
);
}
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
// dispatch在服务端是空函数
function noop() {}为什么服务端Hooks不同?
- 单次渲染:服务端只渲染一次,不需要更新机制
- 无副作用:服务端没有DOM,不需要执行effect
- 性能优化:简化实现,提高渲染速度
- Hydration准备:生成的HTML需要与客户端匹配
服务端与客户端Hooks对比
| Hook | 服务端 | 客户端 |
|---|---|---|
| useState | 返回初始值 | 处理更新队列 |
| useEffect | 不执行 | 异步执行 |
| useLayoutEffect | 不执行 | 同步执行 |
| useMemo | 总是计算 | 缓存结果 |
| useCallback | 总是返回新函数 | 缓存函数 |
| useRef | 返回固定对象 | 持久化引用 |
| useContext | 读取context | 读取context |
| useReducer | 返回初始state | 处理更新队列 |
7.3 useState源码分析
useState是最常用的Hook,它让函数组件拥有状态管理能力。
7.3.1 mountState挂载阶段
首次渲染时,useState调用mountState函数:
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:1894-1920(React 19.3.0)
function mountStateImpl<S>(initialState: (() => S) | S): Hook {
// 1. 创建Hook对象
const hook = mountWorkInProgressHook();
// 2. 处理初始state(支持函数形式)
if (typeof initialState === 'function') {
const initialStateInitializer = initialState;
initialState = initialStateInitializer();
}
// 3. 设置初始state
hook.memoizedState = hook.baseState = initialState;
// 4. 创建更新队列
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
};
hook.queue = queue;
return hook;
}
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountStateImpl(initialState);
const queue = hook.queue;
// 5. 创建dispatch函数
const dispatch: Dispatch<BasicStateAction<S>> =
dispatchSetState.bind(null, currentlyRenderingFiber, queue);
queue.dispatch = dispatch;
// 6. 返回[state, dispatch]
return [hook.memoizedState, dispatch];
}mountState的步骤
- 创建Hook对象:调用mountWorkInProgressHook
- 处理初始state:如果是函数,执行函数获取初始值
- 设置初始state:memoizedState和baseState都设为初始值
- 创建更新队列:用于保存setState创建的更新
- 创建dispatch函数:绑定fiber和queue
- 返回结果:[state, dispatch]
mountWorkInProgressHook
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:979-1000(React 19.3.0)
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// 第一个Hook
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 后续Hook,添加到链表末尾
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}示例:首次渲染useState
jsx
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('React');
return <div>{name}: {count}</div>;
}
// 首次渲染流程:
// 1. 调用useState(0)
// - mountState(0)
// - 创建Hook1,memoizedState = 0
// - 创建dispatch1
// - 返回[0, dispatch1]
// 2. 调用useState('React')
// - mountState('React')
// - 创建Hook2,memoizedState = 'React'
// - 创建dispatch2
// - 返回['React', dispatch2]
// 结果:
// fiber.memoizedState -> Hook1 -> Hook2 -> null7.3.2 updateState更新阶段
更新时,useState调用updateState函数:
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:1936-1940(React 19.3.0)
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, initialState);
}
// basicStateReducer是useState的reducer
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}updateState实际上调用updateReducer
useState是useReducer的特殊情况,reducer固定为basicStateReducer。
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:1100-1200(React 19.3.0,简化版)
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// 1. 获取当前Hook
const hook = updateWorkInProgressHook();
const queue = hook.queue;
// 2. 获取待处理的更新
const pending = queue.pending;
if (pending !== null) {
// 3. 处理更新队列
const baseQueue = hook.baseQueue;
// 合并pending和baseQueue
if (baseQueue !== null) {
const baseFirst = baseQueue.next;
const pendingFirst = pending.next;
baseQueue.next = pendingFirst;
pending.next = baseFirst;
}
hook.baseQueue = baseQueue = pending;
queue.pending = null;
}
if (baseQueue !== null) {
// 4. 遍历更新队列,计算新state
const first = baseQueue.next;
let newState = hook.baseState;
let update = first;
do {
const updateLane = update.lane;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// 优先级不够,跳过这个更新
// 保存到baseQueue,下次渲染时处理
const clone: Update<S, A> = {
lane: updateLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: null,
};
// 添加到新的baseQueue
} else {
// 优先级足够,应用这个更新
if (update.hasEagerState) {
// 使用预计算的state
newState = update.eagerState;
} else {
// 调用reducer计算新state
const action = update.action;
newState = reducer(newState, action);
}
}
update = update.next;
} while (update !== null && update !== first);
// 5. 保存新state
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
}
// 6. 返回[state, dispatch]
const dispatch: Dispatch<A> = queue.dispatch;
return [hook.memoizedState, dispatch];
}updateWorkInProgressHook
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
function updateWorkInProgressHook(): Hook {
// 1. 移动currentHook指针
let nextCurrentHook: null | Hook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = currentHook.next;
}
currentHook = nextCurrentHook;
// 2. 移动workInProgressHook指针
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
// 复用已有的Hook
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
// 克隆currentHook
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}示例:更新useState
jsx
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
// 更新流程:
// 1. 用户点击按钮
// 2. 调用setCount(1)
// - 创建update对象
// - 加入queue.pending
// - 调度更新
// 3. 重新渲染Counter
// 4. 调用useState(0)
// - updateState(0)
// - updateReducer(basicStateReducer, 0)
// - 获取Hook对象
// - 处理更新队列
// - 计算新state: basicStateReducer(0, 1) = 1
// - 返回[1, dispatch]
// 5. 渲染新UI7.3.3 dispatchSetState触发更新
当调用setState时,实际上调用的是dispatchSetState函数:
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:3598-3627(React 19.3.0)
function dispatchSetState<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
): void {
// 1. 请求更新lane
const lane = requestUpdateLane(fiber);
// 2. 调用内部实现
const didScheduleUpdate = dispatchSetStateInternal(
fiber,
queue,
action,
lane,
);
if (didScheduleUpdate) {
startUpdateTimerByLane(lane, 'setState()', fiber);
}
markUpdateInDevTools(fiber, lane, action);
}
function dispatchSetStateInternal<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
lane: Lane,
): boolean {
// 1. 创建update对象
const update: Update<S, A> = {
lane,
revertLane: NoLane,
gesture: null,
action,
hasEagerState: false,
eagerState: null,
next: null,
};
// 2. 判断是否在渲染阶段
if (isRenderPhaseUpdate(fiber)) {
// 在渲染阶段调用setState,加入渲染阶段更新队列
enqueueRenderPhaseUpdate(queue, update);
} else {
const alternate = fiber.alternate;
// 3. 尝试eager state优化
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 队列为空,可以提前计算新state
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
try {
const currentState: S = queue.lastRenderedState;
const eagerState = lastRenderedReducer(currentState, action);
// 保存预计算的state
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// 新state与旧state相同,可以bailout
enqueueConcurrentHookUpdateAndEagerlyBailout(
fiber, queue, update
);
return false;
}
} catch (error) {
// 计算失败,在render阶段重新计算
}
}
}
// 4. 将update加入队列
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
// 5. 调度更新
scheduleUpdateOnFiber(root, fiber, lane);
entangleTransitionUpdate(root, queue, lane);
return true;
}
}
return false;
}dispatchSetState的关键步骤
- 创建update对象:包含action和lane
- eager state优化:如果队列为空,提前计算新state
- bailout优化:如果新state与旧state相同,跳过更新
- 加入更新队列:将update添加到queue.pending
- 调度更新:触发React的调度系统
eager state优化
当队列为空时,React会提前计算新state。如果新state与旧state相同(使用Object.is比较),React可以跳过整个更新流程,这是一个重要的性能优化。
jsx
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(0); // 设置相同的值
};
return <button onClick={handleClick}>{count}</button>;
}
// 执行流程:
// 1. 调用setCount(0)
// 2. 创建update对象,action = 0
// 3. 队列为空,计算eagerState
// eagerState = basicStateReducer(0, 0) = 0
// 4. 比较:is(0, 0) = true
// 5. bailout,不触发更新
// 6. 组件不会重新渲染更新队列的结构
javascript
// queue.pending是一个环形链表
queue.pending -> update3 -> update1 -> update2 -> update3 (环形)
↑ ↓
└──────────────────────────────┘
// pending指向最后一个update
// pending.next指向第一个update示例:多次setState
jsx
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1); // update1
setCount(count + 1); // update2
setCount(count + 1); // update3
};
return <button onClick={handleClick}>{count}</button>;
}
// 执行流程:
// 1. 调用setCount(count + 1)三次
// 2. 创建三个update对象
// update1: action = 1
// update2: action = 1
// update3: action = 1
// 3. 形成环形链表
// queue.pending -> update3 -> update1 -> update2 -> update3
// 4. 重新渲染
// 5. 处理更新队列
// newState = 0
// newState = basicStateReducer(0, 1) = 1
// newState = basicStateReducer(1, 1) = 1
// newState = basicStateReducer(1, 1) = 1
// 6. 最终count = 1(不是3!)
// 正确的做法:使用函数式更新
const handleClick = () => {
setCount(c => c + 1); // update1: action = c => c + 1
setCount(c => c + 1); // update2: action = c => c + 1
setCount(c => c + 1); // update3: action = c => c + 1
};
// 执行流程:
// 1. 处理更新队列
// newState = 0
// newState = (c => c + 1)(0) = 1
// newState = (c => c + 1)(1) = 2
// newState = (c => c + 1)(2) = 3
// 2. 最终count = 3(正确!)7.4 useEffect源码分析
useEffect是React中处理副作用的核心Hook,它在commit阶段异步执行。
7.4.1 Effect数据结构
useEffect使用Effect对象来保存副作用信息:
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:217-233(React 19.3.0)
type EffectInstance = {
destroy: void | (() => void),
};
export type Effect = {
tag: HookFlags,
inst: EffectInstance,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
next: Effect,
};Effect对象的属性
tag:Effect的标记
- HookHasEffect:需要执行
- HookPassive:useEffect(异步)
- HookLayout:useLayoutEffect(同步)
- HookInsertion:useInsertionEffect(DOM变更前)
inst:Effect实例,保存destroy函数
create:用户传入的effect函数
deps:依赖数组
next:指向下一个Effect(环形链表)
7.4.2 mountEffect挂载阶段
首次渲染时,useEffect调用mountEffect:
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:2671-2693(React 19.3.0)
function mountEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps,
);
}
function mountEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
// 1. 创建Hook对象
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 2. 标记Fiber的flags
currentlyRenderingFiber.flags |= fiberFlags;
// 3. 创建Effect对象并保存到Hook
hook.memoizedState = pushSimpleEffect(
HookHasEffect | hookFlags,
createEffectInstance(),
create,
nextDeps,
);
}pushSimpleEffect
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
function pushSimpleEffect(
tag: HookFlags,
inst: EffectInstance,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): Effect {
const effect: Effect = {
tag,
inst,
create,
deps,
next: (null: any),
};
let componentUpdateQueue: null | FunctionComponentUpdateQueue =
(currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) {
// 创建updateQueue
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
// 添加到Effect环形链表
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}Effect环形链表
fiber.updateQueue.lastEffect -> Effect3 -> Effect1 -> Effect2 -> Effect3
↑ ↓
└──────────────────────────────┘7.4.3 updateEffect更新阶段
更新时,useEffect调用updateEffect:
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:2695-2700(React 19.3.0)
function updateEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function updateEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
// 1. 获取当前Hook
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const effect: Effect = hook.memoizedState;
const inst = effect.inst;
// 2. 比较依赖数组
if (currentHook !== null) {
if (nextDeps !== null) {
const prevEffect: Effect = currentHook.memoizedState;
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 依赖没变,不需要执行effect
hook.memoizedState = pushSimpleEffect(
hookFlags, // 没有HookHasEffect标记
inst,
create,
nextDeps,
);
return;
}
}
}
// 3. 依赖变化,需要执行effect
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushSimpleEffect(
HookHasEffect | hookFlags, // 添加HookHasEffect标记
inst,
create,
nextDeps,
);
}areHookInputsEqual(依赖比较)
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
): boolean {
if (prevDeps === null) {
return false;
}
// 逐个比较依赖项(使用Object.is)
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}示例:useEffect的执行时机
jsx
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('React');
useEffect(() => {
console.log('Effect 1: count changed');
return () => {
console.log('Cleanup 1');
};
}, [count]);
useEffect(() => {
console.log('Effect 2: name changed');
return () => {
console.log('Cleanup 2');
};
}, [name]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<button onClick={() => setName('Vue')}>Name: {name}</button>
</div>
);
}
// 首次渲染:
// 1. render阶段:创建两个Effect对象,都标记HookHasEffect
// 2. commit阶段:异步执行
// - Effect 1: count changed
// - Effect 2: name changed
// 点击Count按钮:
// 1. render阶段:
// - Effect 1:deps变化,标记HookHasEffect
// - Effect 2:deps没变,不标记HookHasEffect
// 2. commit阶段:
// - Cleanup 1
// - Effect 1: count changed
// 点击Name按钮:
// 1. render阶段:
// - Effect 1:deps没变,不标记HookHasEffect
// - Effect 2:deps变化,标记HookHasEffect
// 2. commit阶段:
// - Cleanup 2
// - Effect 2: name changed7.4.4 Effect的执行时机
useEffect在commit阶段的layout阶段之后异步执行。
commit阶段的三个子阶段
before mutation阶段:DOM变更前
- 调度useEffect
mutation阶段:执行DOM变更
- 插入、更新、删除DOM
layout阶段:DOM变更后
- 执行useLayoutEffect
- 更新ref
异步执行:layout阶段之后
- 执行useEffect
flushPassiveEffects(执行useEffect)
javascript
// 文件:packages/react-reconciler/src/ReactFiberWorkLoop.js
function flushPassiveEffects(): boolean {
// 1. 执行所有effect的cleanup函数
commitPassiveUnmountEffects(root.current);
// 2. 执行所有effect的create函数
commitPassiveMountEffects(root, root.current);
return true;
}执行顺序
1. 所有组件的cleanup函数(深度优先)
2. 所有组件的effect函数(深度优先)示例:Effect执行顺序
jsx
function Parent() {
useEffect(() => {
console.log('Parent effect');
return () => console.log('Parent cleanup');
});
return <Child />;
}
function Child() {
useEffect(() => {
console.log('Child effect');
return () => console.log('Child cleanup');
});
return <div>Child</div>;
}
// 首次渲染:
// Child effect
// Parent effect
// 更新:
// Child cleanup
// Parent cleanup
// Child effect
// Parent effect
// 卸载:
// Child cleanup
// Parent cleanupuseEffect vs useLayoutEffect
| 特性 | useEffect | useLayoutEffect |
|---|---|---|
| 执行时机 | layout阶段之后,异步 | layout阶段,同步 |
| 阻塞渲染 | 否 | 是 |
| 适用场景 | 数据获取、订阅 | DOM测量、同步更新 |
| 服务端 | 不执行 | 不执行 |
jsx
// useEffect:不阻塞渲染
function AsyncComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(setData);
}, []);
return <div>{data}</div>;
}
// useLayoutEffect:阻塞渲染
function SyncComponent() {
const ref = useRef();
const [height, setHeight] = useState(0);
useLayoutEffect(() => {
// 同步测量DOM
setHeight(ref.current.offsetHeight);
}, []);
return <div ref={ref} style={{ height }}></div>;
}7.5 useMemo与useCallback
useMemo和useCallback是React的性能优化Hook,它们通过缓存计算结果和函数来避免不必要的重新计算。
7.5.1 记忆化实现
mountMemo
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 执行计算函数
const nextValue = nextCreate();
// 保存值和依赖
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}updateMemo
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
// 比较依赖
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 依赖没变,返回缓存的值
return prevState[0];
}
}
}
// 依赖变化,重新计算
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}mountCallback
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 直接保存函数和依赖
hook.memoizedState = [callback, nextDeps];
return callback;
}updateCallback
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
// 比较依赖
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 依赖没变,返回缓存的函数
return prevState[0];
}
}
}
// 依赖变化,返回新函数
hook.memoizedState = [callback, nextDeps];
return callback;
}7.5.2 依赖比较机制
useMemo和useCallback使用相同的依赖比较机制:
javascript
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
): boolean {
if (prevDeps === null) {
return false;
}
// 使用Object.is逐个比较
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}Object.is vs ===
javascript
// Object.is的特殊情况
Object.is(+0, -0); // false
+0 === -0; // true
Object.is(NaN, NaN); // true
NaN === NaN; // false
// 其他情况相同
Object.is(1, 1); // true
Object.is('a', 'a'); // true
Object.is({}, {}); // false(不同引用)示例:useMemo的使用
jsx
function ExpensiveComponent({ items }) {
// ✓ 好:使用useMemo缓存计算结果
const sortedItems = useMemo(() => {
console.log('Sorting items...');
return items.slice().sort((a, b) => a.value - b.value);
}, [items]);
return (
<ul>
{sortedItems.map(item => (
<li key={item.id}>{item.value}</li>
))}
</ul>
);
}
// 执行流程:
// 首次渲染:
// - mountMemo
// - 执行排序
// - 保存结果和依赖
// items没变:
// - updateMemo
// - 比较依赖:areHookInputsEqual([items], [prevItems])
// - 依赖相同,返回缓存的sortedItems
// - 不执行排序
// items变化:
// - updateMemo
// - 比较依赖:依赖不同
// - 重新执行排序
// - 保存新结果示例:useCallback的使用
jsx
function Parent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('React');
// ✗ 差:每次渲染都创建新函数
const handleClick = () => {
console.log(count);
};
// ✓ 好:使用useCallback缓存函数
const handleClickMemo = useCallback(() => {
console.log(count);
}, [count]);
return (
<div>
<Child onClick={handleClick} /> {/* 每次都重新渲染 */}
<ChildMemo onClick={handleClickMemo} /> {/* count没变时不重新渲染 */}
</div>
);
}
const ChildMemo = React.memo(function Child({ onClick }) {
console.log('Child render');
return <button onClick={onClick}>Click</button>;
});useMemo vs useCallback
javascript
// useMemo:缓存计算结果
const value = useMemo(() => computeExpensiveValue(a, b), [a, b]);
// useCallback:缓存函数
const callback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// useCallback等价于:
const callback = useMemo(() => {
return () => {
doSomething(a, b);
};
}, [a, b]);何时使用useMemo和useCallback
- 计算开销大:排序、过滤大数组、复杂计算
- 引用相等性重要:传递给React.memo组件的props
- 作为其他Hook的依赖:useEffect、useMemo的deps
jsx
// ✓ 好:计算开销大
const sortedList = useMemo(() => {
return hugeList.sort((a, b) => a - b);
}, [hugeList]);
// ✗ 差:计算开销小,不值得缓存
const sum = useMemo(() => a + b, [a, b]);
// ✓ 好:传递给React.memo组件
const MemoChild = React.memo(Child);
const handleClick = useCallback(() => {
doSomething();
}, []);
<MemoChild onClick={handleClick} />
// ✗ 差:子组件没有使用React.memo
const handleClick = useCallback(() => {
doSomething();
}, []);
<Child onClick={handleClick} />
// ✓ 好:作为useEffect的依赖
const fetchData = useCallback(() => {
fetch('/api/data');
}, []);
useEffect(() => {
fetchData();
}, [fetchData]);性能陷阱
jsx
// ✗ 差:依赖是对象字面量,每次都不同
const value = useMemo(() => {
return computeExpensiveValue(config);
}, [{ a: 1, b: 2 }]); // 每次都是新对象
// ✓ 好:依赖是原始值
const value = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// ✗ 差:依赖是数组字面量
const value = useMemo(() => {
return computeExpensiveValue(items);
}, [[1, 2, 3]]); // 每次都是新数组
// ✓ 好:依赖是稳定的引用
const items = [1, 2, 3]; // 组件外部
const value = useMemo(() => {
return computeExpensiveValue(items);
}, [items]);7.6 并发Hooks
React 18引入了并发特性,useTransition和useDeferredValue是两个核心的并发Hook。
7.6.1 useTransition实现
useTransition允许将状态更新标记为非紧急的,React可以中断这些更新来处理更紧急的任务。
mountTransition
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
function mountTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
const stateHook = mountStateImpl(false);
const start = startTransition.bind(
null,
currentlyRenderingFiber,
stateHook.queue,
true,
false,
);
const hook = mountWorkInProgressHook();
hook.memoizedState = start;
return [false, start];
}updateTransition
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
function updateTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
const [booleanOrThenable] = updateState(false);
const hook = updateWorkInProgressHook();
const start = hook.memoizedState;
const isPending =
typeof booleanOrThenable === 'boolean'
? booleanOrThenable
: useThenable(booleanOrThenable);
return [isPending, start];
}startTransition
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
function startTransition(
fiber: Fiber,
queue: UpdateQueue<boolean, BasicStateAction<boolean>>,
pendingState: boolean,
finishedState: boolean,
callback: () => void,
options?: StartTransitionOptions,
): void {
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(
higherEventPriority(previousPriority, ContinuousEventPriority),
);
// 设置isPending为true
dispatchSetState(fiber, queue, pendingState);
const prevTransition = ReactSharedInternals.T;
const currentTransition: BatchConfigTransition = {};
ReactSharedInternals.T = currentTransition;
try {
// 设置isPending为false
dispatchSetState(fiber, queue, finishedState);
// 执行transition回调
callback();
} finally {
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
}
}useTransition的工作原理
- 创建isPending状态:用于显示加载状态
- startTransition函数:
- 设置isPending为true
- 降低更新优先级为TransitionLane
- 执行回调函数
- 设置isPending为false
示例:useTransition的使用
jsx
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
// 紧急更新:立即更新输入框
setQuery(value);
// 非紧急更新:延迟更新搜索结果
startTransition(() => {
const filtered = searchData(value);
setResults(filtered);
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<Results data={results} />
</div>
);
}
// 执行流程:
// 1. 用户输入'a'
// 2. setQuery('a'):同步更新,优先级高
// 3. startTransition(() => setResults(...)):
// - 设置isPending = true
// - setResults(...):TransitionLane,优先级低
// - 设置isPending = false
// 4. React先渲染输入框(高优先级)
// 5. 然后渲染搜索结果(低优先级,可中断)7.6.2 useDeferredValue实现
useDeferredValue返回一个延迟版本的值,当有更紧急的更新时,会继续使用旧值。
mountDeferredValue
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
function mountDeferredValue<T>(value: T, initialValue?: T): T {
const hook = mountWorkInProgressHook();
if (initialValue !== undefined && shouldDeferHydration()) {
hook.memoizedState = initialValue;
return initialValue;
}
hook.memoizedState = value;
return value;
}updateDeferredValue
javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
function updateDeferredValue<T>(value: T, initialValue?: T): T {
const hook = updateWorkInProgressHook();
const prevValue: T = hook.memoizedState;
if (is(value, prevValue)) {
// 值没变,返回旧值
return value;
}
// 值变化了
if (isCurrentTreeHidden()) {
// 在Offscreen组件中,立即更新
hook.memoizedState = value;
return value;
}
// 检查是否有紧急更新
const shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);
if (shouldDeferValue) {
// 有紧急更新,继续使用旧值
// 调度一个低优先级更新来更新到新值
const deferredLane = claimNextTransitionLane();
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
deferredLane,
);
markSkippedUpdateLanes(deferredLane);
return prevValue;
} else {
// 没有紧急更新,更新到新值
hook.memoizedState = value;
return value;
}
}useDeferredValue的工作原理
- 首次渲染:返回传入的值
- 值变化时:
- 如果有紧急更新:返回旧值,调度低优先级更新
- 如果没有紧急更新:返回新值
示例:useDeferredValue的使用
jsx
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// query立即更新,deferredQuery延迟更新
const results = useMemo(() => {
return searchData(deferredQuery);
}, [deferredQuery]);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<Results data={results} />
</div>
);
}
// 执行流程:
// 1. 用户输入'a'
// 2. setQuery('a'):query = 'a'
// 3. deferredQuery仍然是''(旧值)
// 4. React先渲染输入框(query = 'a')
// 5. 然后更新deferredQuery = 'a'
// 6. 重新计算results7.6.3 示例:优化搜索体验
使用useTransition
jsx
function SearchWithTransition() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setQuery(value);
startTransition(() => {
// 模拟耗时搜索
const filtered = hugeList.filter(item =>
item.name.includes(value)
);
setResults(filtered);
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending ? (
<Spinner />
) : (
<Results data={results} />
)}
</div>
);
}使用useDeferredValue
jsx
function SearchWithDeferred() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const results = useMemo(() => {
// 模拟耗时搜索
return hugeList.filter(item =>
item.name.includes(deferredQuery)
);
}, [deferredQuery]);
const isStale = query !== deferredQuery;
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<div style={{ opacity: isStale ? 0.5 : 1 }}>
<Results data={results} />
</div>
</div>
);
}useTransition vs useDeferredValue
| 特性 | useTransition | useDeferredValue |
|---|---|---|
| 控制点 | 控制更新函数 | 控制值 |
| isPending | 提供 | 需要自己判断 |
| 适用场景 | 控制setState | 控制props或state |
| 灵活性 | 更灵活 | 更简单 |
jsx
// useTransition:控制更新函数
const [isPending, startTransition] = useTransition();
startTransition(() => {
setState(newValue); // 控制这个更新
});
// useDeferredValue:控制值
const deferredValue = useDeferredValue(value); // 控制这个值并发特性的优势
- 保持响应性:紧急更新不会被阻塞
- 更好的用户体验:输入框立即响应
- 自动优化:React自动处理优先级
- 可中断渲染:低优先级更新可以被打断
jsx
// 传统方式:输入卡顿
function Traditional() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleChange = (e) => {
const value = e.target.value;
setQuery(value);
// 耗时操作阻塞输入
const filtered = hugeList.filter(item =>
item.name.includes(value)
);
setResults(filtered);
};
return (
<div>
<input value={query} onChange={handleChange} />
<Results data={results} />
</div>
);
}
// 并发方式:输入流畅
function Concurrent() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const results = useMemo(() => {
return hugeList.filter(item =>
item.name.includes(deferredQuery)
);
}, [deferredQuery]);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<Results data={results} />
</div>
);
}7.7 示例:Hooks链表图解
让我们通过一个完整的示例来理解Hooks链表的构建和更新过程。
7.7.1 完整示例
jsx
function TodoApp() {
// Hook1: useState
const [todos, setTodos] = useState([]);
// Hook2: useState
const [filter, setFilter] = useState('all');
// Hook3: useMemo
const filteredTodos = useMemo(() => {
if (filter === 'active') {
return todos.filter(t => !t.done);
}
if (filter === 'completed') {
return todos.filter(t => t.done);
}
return todos;
}, [todos, filter]);
// Hook4: useCallback
const addTodo = useCallback((text) => {
setTodos([...todos, { id: Date.now(), text, done: false }]);
}, [todos]);
// Hook5: useEffect
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
// Hook6: useEffect
useEffect(() => {
document.title = `${filteredTodos.length} todos`;
}, [filteredTodos]);
return (
<div>
<TodoInput onAdd={addTodo} />
<FilterButtons filter={filter} setFilter={setFilter} />
<TodoList todos={filteredTodos} />
</div>
);
}7.7.2 首次渲染的Hooks链表
详细结构
javascript
// Fiber节点
fiber = {
memoizedState: Hook1,
updateQueue: {
lastEffect: Effect2,
events: null,
stores: null,
memoCache: null,
},
};
// Hook1: useState([])
Hook1 = {
memoizedState: [],
baseState: [],
baseQueue: null,
queue: {
pending: null,
dispatch: setTodos,
lastRenderedReducer: basicStateReducer,
lastRenderedState: [],
},
next: Hook2,
};
// Hook2: useState('all')
Hook2 = {
memoizedState: 'all',
baseState: 'all',
baseQueue: null,
queue: {
pending: null,
dispatch: setFilter,
lastRenderedReducer: basicStateReducer,
lastRenderedState: 'all',
},
next: Hook3,
};
// Hook3: useMemo
Hook3 = {
memoizedState: [[], [[], 'all']], // [value, deps]
baseState: null,
baseQueue: null,
queue: null,
next: Hook4,
};
// Hook4: useCallback
Hook4 = {
memoizedState: [addTodo, [[]]], // [callback, deps]
baseState: null,
baseQueue: null,
queue: null,
next: Hook5,
};
// Hook5: useEffect
Hook5 = {
memoizedState: {
tag: HookHasEffect | HookPassive,
inst: { destroy: undefined },
create: () => { localStorage.setItem(...); },
deps: [[]],
next: Effect2,
},
baseState: null,
baseQueue: null,
queue: null,
next: Hook6,
};
// Hook6: useEffect
Hook6 = {
memoizedState: {
tag: HookHasEffect | HookPassive,
inst: { destroy: undefined },
create: () => { document.title = ...; },
deps: [[]],
next: Effect1, // 环形链表
},
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
// Effect环形链表
Effect1 -> Effect2 -> Effect1
↑ ↓
└────────────────────┘7.7.3 更新时的Hooks链表
jsx
// 用户添加一个todo
addTodo('Learn Hooks');
// 触发更新,重新渲染TodoApp更新流程
更新后的Hook1
javascript
// Hook1: useState(todos)
Hook1 = {
memoizedState: [{ id: 1, text: 'Learn Hooks', done: false }],
baseState: [{ id: 1, text: 'Learn Hooks', done: false }],
baseQueue: null,
queue: {
pending: null, // 更新已处理
dispatch: setTodos,
lastRenderedReducer: basicStateReducer,
lastRenderedState: [{ id: 1, text: 'Learn Hooks', done: false }],
},
next: Hook2,
};更新后的Hook3
javascript
// Hook3: useMemo(filteredTodos)
Hook3 = {
memoizedState: [
[{ id: 1, text: 'Learn Hooks', done: false }], // 新值
[[{ id: 1, text: 'Learn Hooks', done: false }], 'all'], // 新deps
],
baseState: null,
baseQueue: null,
queue: null,
next: Hook4,
};更新后的Hook5
javascript
// Hook5: useEffect
Hook5 = {
memoizedState: {
tag: HookHasEffect | HookPassive, // 标记需要执行
inst: { destroy: undefined },
create: () => { localStorage.setItem(...); },
deps: [[{ id: 1, text: 'Learn Hooks', done: false }]], // 新deps
next: Effect2,
},
baseState: null,
baseQueue: null,
queue: null,
next: Hook6,
};7.7.4 Hooks调用顺序的重要性
错误示例:条件调用Hooks
jsx
function BadComponent({ condition }) {
if (condition) {
const [state, setState] = useState(0); // ✗ 错误
}
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
// 首次渲染(condition = true):
// Hook1: useState(state)
// Hook2: useState(count)
// 第二次渲染(condition = false):
// Hook1: useState(count) ← 错误!匹配到了state的Hook
// 结果:状态错乱正确示例:顶层调用Hooks
jsx
function GoodComponent({ condition }) {
const [state, setState] = useState(0); // ✓ 正确
const [count, setCount] = useState(0);
if (condition) {
// 使用state
}
return <div>{count}</div>;
}
// 每次渲染:
// Hook1: useState(state)
// Hook2: useState(count)
// 结果:状态正确React如何检测Hooks顺序错误
javascript
// 开发环境下,React会记录Hooks的类型
let hookTypesDev = null;
function mountHookTypesDev() {
const hookName = currentHookNameInDev;
if (hookTypesDev === null) {
hookTypesDev = [hookName];
} else {
hookTypesDev.push(hookName);
}
}
function updateHookTypesDev() {
const hookName = currentHookNameInDev;
if (hookTypesDev !== null) {
const expectedHookName = hookTypesDev[hookTypesUpdateIndexDev];
if (hookName !== expectedHookName) {
// 检测到Hooks顺序错误
warnOnHookMismatchInDev(expectedHookName, hookName);
}
hookTypesUpdateIndexDev++;
}
}本章小结
本章深入讲解了React Hooks的实现原理,让我们回顾一下关键要点:
Hooks的数据结构
- Hook对象:保存Hook的状态和更新队列
- Effect对象:保存副作用函数和依赖
- Hooks链表:通过next指针连接,保证调用顺序
Dispatcher机制
- ContextOnlyDispatcher:非渲染阶段,抛出错误
- HooksDispatcherOnMount:首次渲染,创建Hook
- HooksDispatcherOnUpdate:更新时,复用Hook
核心Hooks实现
- useState:基于useReducer实现,支持eager state优化
- useEffect:异步执行,支持依赖比较和cleanup
- useMemo/useCallback:缓存值和函数,避免不必要的计算
- useTransition/useDeferredValue:并发特性,优化用户体验
关键原则
- Hooks必须在顶层调用:保证调用顺序一致
- 依赖数组很重要:决定是否重新执行
- 引用相等性:使用Object.is比较依赖
- 性能优化:合理使用useMemo和useCallback
思考题
- 为什么Hooks必须在函数组件顶层调用?
- useState的eager state优化在什么情况下生效?
- useEffect和useLayoutEffect的执行时机有什么区别?
- useTransition和useDeferredValue应该如何选择?
在下一章中,我们将进入服务端篇,学习React 19的核心特性:流式SSR(Fizz)。