在 React 16.8 引入 Hooks 之前,函数组件:
这导致开发者更倾向于使用类组件,尽管它们语法复杂、this 上下文混乱。
Facebook 团队于 2019 年正式发布 React 16.8,引入了 Hooks API:
useState
:让函数组件拥有状态;useEffect
:处理副作用;useContext
、useReducer
、useCallback
等辅助 Hook;⚽老曹开发心得:react18入门简单,只要学会useState,useEffect,useRef三个万精油就可以解决大部分业务了,剩下hooks都是性能优化方面的
React Hooks 的实现主要位于以下源码文件中:
文件路径 | 功能描述 |
---|---|
packages/react-reconciler/src/ReactFiberHooks.js |
Hooks 的核心实现,包括 useState、useEffect 等 |
packages/react-reconciler/src/ReactFiberHooks.old.js |
兼容旧版 Fiber 实现 |
packages/react-reconciler/src/ReactUpdateQueue.js |
更新队列的创建与处理,支持 useEffect 的调度 |
packages/react/src/ReactHooks.js |
对外暴露的 Hook API 接口 |
function Hook() {
this.memoizedState = null; // 存储当前 Hook 的状态(如 state、effect)
this.baseState = null; // 基础状态(用于更新合并)
this.baseQueue = null; // 基础更新队列
this.queue = null; // 当前 Hook 的更新队列
this.next = null; // 指向下一个 Hook 节点(形成链表)
}
fiber.memoizedState
,它是一个 Hook 链表头节点;fiber.memoizedState
↓
Hook A (useState)
↓
Hook B (useEffect)
↓
Hook C (useRef)
↓
null
const [count, setCount] = useState(0);
function mountState(initialState) {
const hook = mountWorkInProgressHook(); // 创建新 Hook
hook.memoizedState = initialState;
const queue = { pending: null }; // 创建更新队列
hook.queue = queue;
const dispatch = dispatchAction.bind(null, queue); // 创建 dispatch 函数
return [hook.memoizedState, dispatch];
}
mountState
function mountState(initialState) {
initialState
:用户传入的初始状态值(如 0
、''
、{}
等)。 const hook = mountWorkInProgressHook();
hook.memoizedState = initialState;
memoizedState
字段中。 const queue = { pending: null };
pending
: 指向当前未处理的更新操作(环形链表结构)。 hook.queue = queue;
const dispatch = dispatchAction.bind(null, queue);
dispatch
函数,供用户调用 setCount
时触发状态更新。queue
是当前 Hook 的更新队列。 return [hook.memoizedState, dispatch];
[state, setState]
,供用户解构使用。hook.memoizedState
:当前状态;dispatch
:状态更新函数(即 setCount
);function updateState() {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
const baseQueue = hook.baseQueue;
const pendingQueue = queue.pending;
// 合并更新队列
if (pendingQueue !== null) {
hook.baseQueue = baseQueue.next = pendingQueue;
queue.pending = null;
}
const newState = processUpdateQueue(baseQueue, hook.memoizedState);
hook.memoizedState = newState;
return [newState, queue.dispatch];
}
updateState
function updateState() {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
const baseQueue = hook.baseQueue;
const pendingQueue = queue.pending;
setState
触发)。 if (pendingQueue !== null) {
hook.baseQueue = baseQueue.next = pendingQueue;
queue.pending = null;
}
const newState = processUpdateQueue(baseQueue, hook.memoizedState);
processUpdateQueue
会遍历整个更新链表,依次应用每个更新动作(如 setCount(count + 1)
)。 hook.memoizedState = newState;
return [newState, queue.dispatch];
[state, setState]
,供用户使用。为函数组件提供一种声明式管理状态的能力,支持:
useState
;useEffect
监听 state 变化);设计思想 | 实现方式 |
---|---|
链表结构 | 每个 Hook 是链表节点,通过 next 指针连接 |
双缓冲机制 | current 和 workInProgress Hook 分离 |
更新队列 | 使用环形链表保存所有状态更新 |
异步调度 | 使用 React 的任务调度器控制执行时机 |
不可变性 | 每次更新生成新状态,避免直接修改原状态 |
useState
调用都会创建一个 Hook;next
指针串联成链表;initialState
存入 hook.memoizedState
;queue
,并将其绑定到 Hook 上;[state, setState]
,供用户使用。setState(newValue)
→ 调用 dispatchAction(queue, action)
;action
表示一次状态更新操作(可能是函数或值);queue.pending
队列中,等待下一轮处理。updateState
;[newState, dispatch]
,供用户使用;useState(initialState)
↓
mountState / updateState
↓
mountWorkInProgressHook / updateWorkInProgressHook
↓
创建/复用 Hook 链表节点
↓
dispatchAction → 加入 queue.pending
↓
processUpdateQueue → 应用所有 action 得到 newState
↓
返回 [newState, dispatch]
以下是对 useEffect
内部实现代码的逐行详细注释,以及对其算法设计思想与执行流程步骤的完整总结。
useEffect(() => {
console.log('mounted or updated');
return () => {
console.log('cleanup');
};
}, [deps]);
create
: 用户传入的副作用函数;return () => {}
: 可选的清理函数;[deps]
: 依赖项数组,决定是否重新执行 effect;[]
表示只在组件挂载和卸载时执行;mountEffect
function mountEffect(create, deps) {
useEffect
副作用;create
: 用户传入的副作用函数;deps
: 依赖项数组(可为空); const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
null
,表示每次都执行 effect; hook.memoizedState = pushEffect(HookHasEffect | HookLayout, create, undefined, nextDeps);
HookHasEffect | HookLayout
: 标记该 effect 需要执行;create
: 用户传入的副作用函数;undefined
: 清理函数初始为空;nextDeps
: 当前依赖项数组;updateEffect
function updateEffect(create, deps) {
create
: 用户传入的副作用函数;deps
: 新传入的依赖项数组; const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevEffect = hook.memoizedState;
create
函数和 deps
; const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
areHookInputsEqual
是浅比较(shallow equal); hook.memoizedState = pushEffect(HookNone, create, undefined, nextDeps);
HookNone
: 表示不需要执行 effect; } else {
hook.memoizedState = pushEffect(HookHasEffect | HookLayout, create, undefined, nextDeps);
HookHasEffect
表示需要调度执行该 effect; }
export type Effect = {
tag: number, // 标记 effect 是否执行或清理
create: () => mixed, // 用户传入的副作用函数
destroy: (() => mixed) | null, // 清理函数
deps: Array<mixed> | null, // 依赖项数组
next: Effect, // 指向下一个 Effect,形成链表
};
字段名 | 含义 |
---|---|
tag |
表示 effect 的状态标志(如 HookHasEffect , HookNone ) |
create |
用户传入的副作用函数 |
destroy |
上次执行后的清理函数(可能为 null) |
deps |
依赖项数组,用于判断是否重新执行 effect |
next |
指向下一个 Effect,构成链表结构 |
提供一种声明式副作用管理机制,支持:
设计思想 | 实现方式 |
---|---|
链表结构 | 每个 Hook 是链表节点,通过 next 指针连接 |
双缓冲机制 | current 和 workInProgress Hook 分离 |
副作用队列 | 所有 effect 被收集到 effect list 中统一提交 |
依赖项比较 | 浅比较 deps 判断是否重新执行 effect |
生命周期分离 | useLayoutEffect 在 DOM 更新前执行,useEffect 在之后异步执行 |
useEffect
都会创建一个 Hook 节点;create
函数、deps
依赖;tag
标志位为 HookHasEffect
,表示需要执行;memoizedState
属性中;areHookInputsEqual
进行浅比较;HookHasEffect
的 effect 会被收集到 effect list
;以下是 useEffect
模拟实现的完整算法步骤代码,按照列出的 6 个关键步骤 进行模块化组织,每个步骤对应一个功能模块,并附有详细注释说明其作用和逻辑。
// =================== Hook 结构定义 ===================
function Hook() {
this.memoizedState = null; // 存储 Effect 对象
this.next = null; // 下一个 Hook 节点
}
// =================== 创建/获取当前 Hook 节点(链表结构)===================
let currentlyRenderingFiber = {
memoizedState: null, // 当前 Fiber 的 Hook 链表头节点
};
let workInProgressHook = null;
function mountWorkInProgressHook() {
const hook = new Hook();
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = hook;
} else {
workInProgressHook.next = hook;
}
workInProgressHook = hook;
return hook;
}
function updateWorkInProgressHook() {
let currentHook;
if (workInProgressHook === null) {
currentHook = currentlyRenderingFiber.memoizedState;
workInProgressHook = {
memoizedState: currentHook.memoizedState,
next: null,
};
} else {
currentHook = workInProgressHook;
workInProgressHook = workInProgressHook.next;
}
return currentHook;
}
Hook
是链表中的每一个节点;currentlyRenderingFiber.memoizedState
是 Hook 链表的起始点;mountWorkInProgressHook()
和 updateWorkInProgressHook()
分别用于初始化与更新阶段获取或创建 Hook;// =================== Effect 对象定义 ===================
function Effect(tag, create, destroy, deps) {
this.tag = tag; // 标记是否需要执行 effect
this.create = create; // 用户传入的副作用函数
this.destroy = destroy; // 清理函数
this.deps = deps; // 依赖项数组
this.next = null; // 下一个 Effect
}
const HookHasEffect = /* */ 0b0000000001;
const HookLayout = /* layout effect */ 0b0000000010;
function mountEffect(create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const effect = new Effect(HookHasEffect | HookLayout, create, undefined, nextDeps);
hook.memoizedState = effect;
pushEffect(effect); // 收集到 effect list 中
}
Effect
类封装了副作用的核心信息;tag
为 HookHasEffect | HookLayout
表示该 effect 需要执行;create
是用户传入的副作用函数;deps
是依赖项数组;pushEffect(effect)
将当前 effect 加入全局 effect list,等待后续提交;function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null || nextDeps === null) {
return false; // 如果是首次或无依赖项,则执行
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (Object.is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
function updateEffect(create, deps) {
const hook = updateWorkInProgressHook();
const prevEffect = hook.memoizedState;
const nextDeps = deps === undefined ? null : deps;
if (areHookInputsEqual(nextDeps, prevEffect.deps)) {
// 依赖未变化,跳过执行 effect
const effect = new Effect(HookNone, create, prevEffect.destroy, nextDeps);
hook.memoizedState = effect;
} else {
// 依赖变化,标记为需执行
const effect = new Effect(HookHasEffect | HookLayout, create, prevEffect.destroy, nextDeps);
hook.memoizedState = effect;
pushEffect(effect);
}
}
areHookInputsEqual()
实现浅比较,判断依赖项是否变化;HookNone
);HookHasEffect
)并加入 effect list;hook.memoizedState
保存新的 effect;// =================== 模拟 effect list 提交队列 ===================
const effectList = [];
function pushEffect(effect) {
effectList.push(effect);
}
effectList
是一个全局数组,用于存储所有被标记为“需要执行”的 effect;// =================== 模拟提交阶段 ===================
function flushPassiveEffects() {
for (const effect of effectList) {
if (effect.tag & HookHasEffect) {
// 执行上一次的清理函数
if (effect.destroy) {
effect.destroy();
}
// 执行新的副作用
effect.destroy = effect.create();
// 更新 effect list
effect.tag &= ~HookHasEffect;
}
}
}
flushPassiveEffects()
模拟 commit 阶段;create()
副作用函数;destroy
字段,供下一轮使用;HookHasEffect
标志位,避免重复执行;if (module.hot) {
module.hot.accept();
module.hot.dispose(() => {
// 模拟组件卸载
workInProgressHook = null;
currentlyRenderingFiber.memoizedState = null;
});
}
module.hot.dispose()
模拟组件卸载;destroy
方法;// =================== 示例组件 ===================
function MyComponent() {
const [count, setCount] = myUseState(0);
myUseEffect(() => {
console.log('mounted or updated', count);
return () => {
console.log('cleanup', count);
};
}, [count]);
const button = document.getElementById('btn');
button.onclick = () => {
setCount(count + 1);
simulateRender(); // 触发重新渲染
};
console.log('Current effect list:', effectList);
}
// =================== 模拟 useState ===================
let stateIndex = 0;
const memoizedState = [];
function myUseState(initialState) {
const currentIndex = stateIndex++;
if (memoizedState[currentIndex] === undefined) {
memoizedState[currentIndex] = initialState;
}
function setState(newState) {
memoizedState[currentIndex] = newState;
simulateRender();
}
return [memoizedState[currentIndex], setState];
}
// =================== 模拟重新渲染 ===================
function simulateRender() {
console.log('--- Re-rendering ---');
stateIndex = 0;
workInProgressHook = null;
renderWithHooks();
flushPassiveEffects();
}
function renderWithHooks() {
workInProgressHook = null;
currentlyRenderingFiber.memoizedState = null;
MyComponent(); // 执行组件函数,触发 useEffect
}
MyComponent()
是一个简化版函数组件;myUseState
模拟状态管理;myUseEffect
是对 mountEffect / updateEffect
的封装;setCount()
后都会调用 simulateRender()
模拟重新渲染;步骤编号 | 功能描述 | 对应代码文件位置 |
---|---|---|
Step 1 | 创建 Hook 链表节点 | Hook, mountWorkInProgressHook, updateWorkInProgressHook |
Step 2 | 初始化 Effect | Effect, mountEffect, pushEffect |
Step 3 | 更新时比较依赖项 | updateEffect, areHookInputsEqual |
Step 4 | 收集 effect list | effectList , pushEffect |
Step 5 | 执行与清理 | flushPassiveEffects |
Step 6 | 组件卸载时清理所有 effect | module.hot.dispose |
useEffect(create, deps)
↓
mountEffect / updateEffect
↓
mountWorkInProgressHook / updateWorkInProgressHook
↓
创建/复用 Hook 节点
↓
pushEffect → 构建 Effect 对象
↓
比较 deps → 决定是否执行 effect
↓
commitRoot → 提交 effect list
↓
执行 create 函数 + 清理逻辑
useLayoutEffect
useEffect
const ref = useRef(0);
以下是对 mountRef
和 updateRef
函数的逐行详细注释,以及完整的代码片段。这两个函数模拟了 React 的 useRef
Hook 的内部实现机制。
/**
* 模拟 React 内部 mount 阶段的 useRef 实现
* 用于组件首次渲染时创建一个 ref 对象
*/
function mountRef(initialValue) {
// 1. 创建一个新的 Hook 节点,并添加到当前 Fiber 的 Hook 链表中
const hook = mountWorkInProgressHook();
// 2. 创建 ref 对象,包含一个 current 属性,初始值为 initialValue
const ref = { current: initialValue };
// 3. 将 ref 对象保存在 hook.memoizedState 中,供后续更新阶段使用
hook.memoizedState = ref;
// 4. 返回 ref 对象,供开发者使用
return ref;
}
/**
* 模拟 React 内部 update 阶段的 useRef 实现
* 用于组件后续更新时获取已存在的 ref 对象
*/
function updateRef(initialValue) {
// 1. 获取当前正在处理的 Hook 节点(复用已有 Hook)
const hook = updateWorkInProgressHook();
// 2. 返回该 Hook 上次保存的 ref 对象(不会重新创建)
return hook.memoizedState;
}
为了使 mountRef
和 updateRef
可运行,需要配合以下辅助结构和函数:
// Hook 类:表示一个 Hook 节点
function Hook() {
this.memoizedState = null; // 存储 Hook 的状态(如 ref、state 等)
this.next = null; // 指向下一个 Hook 节点,构成链表结构
}
// 当前 Fiber 节点(模拟)
let currentlyRenderingFiber = {
memoizedState: null, // Hook 链表头节点
};
// 当前正在构建或更新的 Hook 节点
let workInProgressHook = null;
/**
* 初始化阶段:创建一个新的 Hook 并加入链表
*/
function mountWorkInProgressHook() {
const hook = new Hook();
if (workInProgressHook === null) {
// 如果是第一个 Hook,则作为链表头节点
currentlyRenderingFiber.memoizedState = hook;
} else {
// 否则链接到上一个 Hook 的 next 属性上
workInProgressHook.next = hook;
}
workInProgressHook = hook;
return hook;
}
/**
* 更新阶段:复用已有的 Hook
*/
function updateWorkInProgressHook() {
let currentHook;
if (workInProgressHook === null) {
// 第一次调用时从 current Fiber 获取 Hook
currentHook = currentlyRenderingFiber.memoizedState;
workInProgressHook = {
memoizedState: currentHook.memoizedState,
next: null,
};
} else {
// 后续 Hook 直接取当前节点并移动指针
currentHook = workInProgressHook;
workInProgressHook = workInProgressHook.next;
}
return currentHook;
}
useRef
function MyComponent() {
// 使用自定义 useRef 创建一个 ref 对象
const inputRef = mountRef(null);
// 模拟点击按钮聚焦输入框
const button = document.getElementById('btn');
button.onclick = () => {
if (inputRef.current) {
inputRef.current.focus();
console.log('Input focused:', inputRef.current.value);
}
};
// 渲染输入框
const input = document.createElement('input');
input.id = 'my-input';
input.placeholder = '请输入内容';
input.ref = inputRef; // 模拟 ref 绑定
inputRef.current = input; // 手动赋值 ref.current
document.body.appendChild(input);
}
// =================== 初始化渲染 ===================
MyComponent(); // 初始挂载组件
设计点 | 说明 |
---|---|
不触发重新渲染 | ref 的变化不会导致组件重新渲染 |
保持引用稳定性 | 即使组件多次渲染,返回的 ref 始终是同一个对象 |
跨渲染周期共享数据 | 可以用来保存 DOM 引用、定时器 ID、任意可变状态等 |
支持自定义 Hook 复用逻辑 | 如封装 usePrevious(value) |
if (condition) {
const [state, setState] = useState(0); // ❌ 不允许
}
useEffect
、useMemo
、useCallback
等 Hook 都支持依赖项;useEffect(() => {
console.log('依赖项变化了');
}, [obj]); // 浅比较 obj 是否引用改变
React Hooks 的实现中体现了多个经典设计模式:
设计模式 | 应用场景 |
---|---|
链表结构(Linked List) | Hook 链表保存组件状态 |
双缓冲机制(Double Buffering) | current 与 workInProgress Fiber 的 Hook 切换 |
策略模式(Strategy) | 不同 Hook 类型(useState、useEffect)采用不同处理策略 |
观察者模式(Observer) | 依赖项变化触发 Hook 重新执行 |
命令模式(Command) | 将 effect 作为命令放入队列,延迟执行 |
享元模式(Flyweight) | 复用已有的 Hook 节点,避免重复创建 |
本教程深入剖析了 React Hooks 的底层实现机制,包括:
useState
的状态管理与更新队列;useEffect
的副作用调度与清理逻辑;useRef
的引用保持机制;