Appearance
Hooks链表结构
学习目标
通过本章学习,你将深入理解:
- React Hooks的底层数据结构
- Fiber节点与Hooks的关系
- Hooks链表的工作原理
- 为什么Hooks必须在顶层调用
- Hooks的初始化和更新流程
- 多个Hooks如何组织和管理
- Hooks链表的遍历机制
- React 19中的优化
第一部分:Hooks数据结构基础
1.1 Hook对象的结构
每个Hook在React内部都表示为一个Hook对象:
javascript
// Hook对象的基本结构
type Hook = {
memoizedState: any, // 存储Hook的状态值
baseState: any, // 基础状态
baseQueue: Update<any> | null, // 基础更新队列
queue: UpdateQueue<any> | null, // 更新队列
next: Hook | null // 指向下一个Hook
};各字段含义:
javascript
// 1. memoizedState - 存储不同类型Hook的状态
useState: {
memoizedState: state值
}
useEffect: {
memoizedState: {
create: effect函数,
destroy: cleanup函数,
deps: 依赖数组,
next: 下一个effect,
tag: effect标记
}
}
useMemo: {
memoizedState: [缓存的值, 依赖数组]
}
useRef: {
memoizedState: { current: 引用的值 }
}
// 2. baseState - 基础状态(用于状态更新)
// 3. baseQueue - 基础更新队列
// 4. queue - 当前更新队列
// 5. next - 链表的下一个节点1.2 Fiber节点与Hooks
Hooks挂载在Fiber节点上:
javascript
// Fiber节点简化结构
type Fiber = {
// ...其他属性
// 函数组件的Hooks链表
memoizedState: Hook | null, // 指向第一个Hook
// 更新队列
updateQueue: UpdateQueue | null,
// 组件类型和函数
type: Function,
stateNode: any
};
// 示例:组件与Fiber的关系
function Component() {
const [count, setCount] = useState(0); // Hook 1
const [name, setName] = useState(''); // Hook 2
useEffect(() => {}, []); // Hook 3
return <div>{count} - {name}</div>;
}
// 对应的Fiber结构:
// Fiber.memoizedState → Hook1 → Hook2 → Hook3 → null
// (count) (name) (effect)1.3 链表结构可视化
Fiber节点
|
| memoizedState
↓
Hook1 (useState)
| next
↓
Hook2 (useState)
| next
↓
Hook3 (useEffect)
| next
↓
Hook4 (useMemo)
| next
↓
null完整示例:
javascript
function UserProfile() {
// Hook 1
const [user, setUser] = useState(null);
// Hook 2
const [loading, setLoading] = useState(true);
// Hook 3
useEffect(() => {
fetchUser().then(setUser);
}, []);
// Hook 4
const displayName = useMemo(() => {
return user?.name || '匿名';
}, [user]);
// Hook 5
const handleUpdate = useCallback(() => {
updateUser(user);
}, [user]);
return <div>{displayName}</div>;
}
// Fiber.memoizedState链表:
// Hook1(user) → Hook2(loading) → Hook3(effect) → Hook4(displayName) → Hook5(handleUpdate) → null第二部分:Hooks初始化流程
2.1 首次渲染(Mount阶段)
初始化过程:
javascript
// 简化的mountState实现
function mountState(initialState) {
// 1. 创建新的Hook对象
const hook = {
memoizedState: initialState,
baseState: initialState,
baseQueue: null,
queue: {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState
},
next: null
};
// 2. 将Hook添加到Fiber的链表中
if (workInProgressHook === null) {
// 第一个Hook
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 后续Hook,添加到链表末尾
workInProgressHook = workInProgressHook.next = hook;
}
// 3. 创建dispatch函数
const dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber,
hook.queue
);
hook.queue.dispatch = dispatch;
// 4. 返回状态和dispatch
return [hook.memoizedState, dispatch];
}
// 使用示例
function Component() {
// 第一次调用useState
const [count, setCount] = useState(0);
// → 创建Hook1,挂载到Fiber.memoizedState
// 第二次调用useState
const [name, setName] = useState('');
// → 创建Hook2,Hook1.next = Hook2
return <div>{count} - {name}</div>;
}初始化流程图:
1. 调用useState(0)
↓
2. mountState被调用
↓
3. 创建Hook对象
{
memoizedState: 0,
next: null,
queue: { ... }
}
↓
4. 添加到Fiber链表
Fiber.memoizedState = Hook1
↓
5. 返回 [0, setState]
再次调用useState('')
↓
mountState被调用
↓
创建Hook对象
{
memoizedState: '',
next: null,
queue: { ... }
}
↓
添加到链表
Hook1.next = Hook2
↓
返回 ['', setState]2.2 更新渲染(Update阶段)
javascript
// 简化的updateState实现
function updateState(initialState) {
// 1. 获取当前Hook(从链表中)
const hook = updateWorkInProgressHook();
// 2. 处理更新队列
const queue = hook.queue;
const pending = queue.pending;
if (pending !== null) {
// 3. 计算新状态
let newState = hook.memoizedState;
let update = pending.next;
do {
// 应用每个更新
const action = update.action;
newState = typeof action === 'function'
? action(newState)
: action;
update = update.next;
} while (update !== pending.next);
// 4. 更新Hook状态
hook.memoizedState = newState;
hook.baseState = newState;
queue.pending = null;
}
// 5. 返回状态和dispatch
const dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}
// updateWorkInProgressHook - 获取下一个Hook
function updateWorkInProgressHook() {
// 从current树获取对应的Hook
let nextCurrentHook;
if (currentHook === null) {
// 第一个Hook
const current = currentlyRenderingFiber.alternate;
nextCurrentHook = current.memoizedState;
} else {
// 后续Hook,沿着链表向下
nextCurrentHook = currentHook.next;
}
currentHook = nextCurrentHook;
// 创建workInProgress Hook
const newHook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null
};
// 添加到workInProgress链表
if (workInProgressHook === null) {
workInProgressHook = newHook;
currentlyRenderingFiber.memoizedState = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
return workInProgressHook;
}更新流程图:
组件重新渲染
↓
1. 调用useState()
↓
2. updateState被调用
↓
3. 从Fiber链表获取对应Hook
currentHook = Fiber.alternate.memoizedState (第一个Hook)
↓
4. 处理pending更新
应用所有update
↓
5. 返回新状态
[newState, dispatch]
↓
6. 继续调用下一个useState()
↓
7. 获取下一个Hook
currentHook = currentHook.next
↓
8. 重复步骤4-5第三部分:为什么Hooks必须在顶层调用
3.1 规则的本质原因
Hooks依赖于调用顺序来维护状态对应关系。
错误示例:
javascript
// ❌ 错误:条件调用Hook
function BadComponent({ condition }) {
if (condition) {
const [state1, setState1] = useState(0); // 有时调用
}
const [state2, setState2] = useState(''); // 总是调用
return <div>{state2}</div>;
}
// 问题分析:
// 首次渲染(condition = true):
// Hook链表: Hook1(state1) → Hook2(state2) → null
// 第二次渲染(condition = false):
// Hook链表: Hook1(state2) → null
// ❌ state2对应到了Hook1的位置!状态错乱!正确示例:
javascript
// ✅ 正确:始终调用Hook
function GoodComponent({ condition }) {
const [state1, setState1] = useState(0);
const [state2, setState2] = useState('');
// 根据条件使用状态
const displayState = condition ? state1 : state2;
return <div>{displayState}</div>;
}
// Hook链表始终一致:
// 任何渲染: Hook1(state1) → Hook2(state2) → null3.2 链表顺序的重要性
javascript
// 示例:多个Hooks的顺序
function Component() {
const [count, setCount] = useState(0); // 位置 0
const [name, setName] = useState(''); // 位置 1
useEffect(() => {}, []); // 位置 2
const value = useMemo(() => count * 2, [count]); // 位置 3
// Hook链表:
// 0: Hook(useState, count)
// 1: Hook(useState, name)
// 2: Hook(useEffect)
// 3: Hook(useMemo, value)
return <div>{count} - {name} - {value}</div>;
}
// 更新时React会:
// 1. 从位置0获取Hook → count
// 2. 从位置1获取Hook → name
// 3. 从位置2获取Hook → effect
// 4. 从位置3获取Hook → value
// 如果顺序改变,状态就会错乱!3.3 循环和条件的问题
javascript
// ❌ 错误:循环中调用Hook
function BadLoop({ items }) {
items.forEach(item => {
const [state, setState] = useState(item.value); // 错误!
});
}
// 问题:
// items = [1, 2, 3]时:Hook1 → Hook2 → Hook3
// items = [1, 2]时:Hook1 → Hook2
// Hook数量不一致导致错误!
// ✅ 正确:固定数量的Hook
function GoodLoop({ items }) {
const [states, setStates] = useState(
items.map(item => item.value)
);
return states.map((state, index) => (
<div key={index}>{state}</div>
));
}第四部分:不同Hook的链表节点
4.1 useState的Hook结构
javascript
// useState Hook
{
memoizedState: currentValue, // 当前状态值
baseState: baseValue, // 基础状态
baseQueue: null, // 基础队列
queue: { // 更新队列
pending: null, // 待处理更新
dispatch: setStateFunction, // setState函数
lastRenderedReducer: basicStateReducer,
lastRenderedState: currentValue
},
next: nextHook // 下一个Hook
}
// 示例
function Component() {
const [count, setCount] = useState(0);
// 对应的Hook:
// {
// memoizedState: 0,
// queue: { dispatch: setCount, ... },
// next: ...
// }
}4.2 useEffect的Hook结构
javascript
// useEffect Hook
{
memoizedState: {
tag: effectTag, // effect标记(HookHasEffect等)
create: effectFunction, // effect函数
destroy: cleanupFunction, // cleanup函数
deps: dependencyArray, // 依赖数组
next: nextEffect // 下一个effect(effect链表)
},
baseState: null,
baseQueue: null,
queue: null,
next: nextHook // 下一个Hook
}
// 示例
function Component() {
useEffect(() => {
// effect函数
console.log('effect');
return () => {
// cleanup函数
console.log('cleanup');
};
}, [dependency]);
// 对应的Hook:
// {
// memoizedState: {
// create: effectFunction,
// destroy: cleanupFunction,
// deps: [dependency],
// ...
// }
// }
}4.3 useMemo和useCallback的Hook结构
javascript
// useMemo/useCallback Hook
{
memoizedState: [value, deps], // [缓存的值, 依赖数组]
baseState: null,
baseQueue: null,
queue: null,
next: nextHook
}
// useMemo示例
function Component() {
const expensive = useMemo(() => {
return heavyComputation();
}, [dependency]);
// Hook结构:
// {
// memoizedState: [computedValue, [dependency]],
// next: ...
// }
}
// useCallback示例(本质是useMemo的特殊形式)
function Component() {
const callback = useCallback(() => {
doSomething();
}, [dependency]);
// Hook结构:
// {
// memoizedState: [callbackFunction, [dependency]],
// next: ...
// }
}4.4 useRef的Hook结构
javascript
// useRef Hook
{
memoizedState: { current: value }, // ref对象
baseState: null,
baseQueue: null,
queue: null,
next: nextHook
}
// 示例
function Component() {
const ref = useRef(initialValue);
// Hook结构:
// {
// memoizedState: { current: initialValue },
// next: ...
// }
// 更新ref.current不会创建新Hook
ref.current = newValue; // 直接修改memoizedState.current
}第五部分:Hooks链表的遍历
5.1 初始化遍历
javascript
// 首次渲染时构建链表
let currentlyRenderingFiber = null;
let workInProgressHook = null;
function renderWithHooks(fiber) {
currentlyRenderingFiber = fiber;
fiber.memoizedState = null; // 重置链表
workInProgressHook = null;
// 执行组件函数
const Component = fiber.type;
const props = fiber.pendingProps;
const children = Component(props); // 这里会调用所有Hooks
return children;
}
// 组件执行时
function Component() {
// 每次调用Hook都会创建节点并添加到链表
const [state1] = useState(0); // Hook1
const [state2] = useState(''); // Hook2
useEffect(() => {}); // Hook3
// 最终链表:Hook1 → Hook2 → Hook3 → null
}5.2 更新时的遍历
javascript
// 更新渲染时遍历链表
let currentHook = null; // current树的Hook
let workInProgressHook = null; // workInProgress树的Hook
function renderWithHooks(fiber) {
const current = fiber.alternate;
// 设置current Hook链表的起点
if (current !== null) {
currentHook = current.memoizedState;
}
currentlyRenderingFiber = fiber;
fiber.memoizedState = null;
workInProgressHook = null;
// 执行组件
const children = Component(props);
return children;
}
// 每次调用Hook时
function updateWorkInProgressHook() {
// 1. 从current树获取Hook
const nextCurrentHook = currentHook !== null
? currentHook.next
: null;
// 2. 移动current指针
currentHook = nextCurrentHook;
// 3. 创建workInProgress Hook
const newHook = {
memoizedState: nextCurrentHook.memoizedState,
// ...
next: null
};
// 4. 添加到workInProgress链表
if (workInProgressHook === null) {
workInProgressHook = newHook;
fiber.memoizedState = newHook;
} else {
workInProgressHook.next = newHook;
workInProgressHook = newHook;
}
return workInProgressHook;
}5.3 链表遍历的完整流程
javascript
// 完整示例
function Component() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
useEffect(() => {});
return <div>{count} - {name}</div>;
}
// 初始渲染:
// 1. 调用useState(0)
// → mountState → 创建Hook1 → Fiber.memoizedState = Hook1
// 2. 调用useState('')
// → mountState → 创建Hook2 → Hook1.next = Hook2
// 3. 调用useEffect
// → mountEffect → 创建Hook3 → Hook2.next = Hook3
// 最终:Hook1 → Hook2 → Hook3 → null
// 更新渲染:
// 1. 设置currentHook = Fiber.alternate.memoizedState (Hook1)
// 2. 调用useState(0)
// → updateState → 从currentHook获取 → 创建新Hook1
// → currentHook = Hook1.next (Hook2)
// 3. 调用useState('')
// → updateState → 从currentHook获取 → 创建新Hook2
// → currentHook = Hook2.next (Hook3)
// 4. 调用useEffect
// → updateEffect → 从currentHook获取 → 创建新Hook3
// → currentHook = Hook3.next (null)第六部分:React 19的优化
6.1 Hooks优化
React 19对Hooks链表进行了多项优化:
javascript
// 1. 更高效的Hook查找
// React 18及之前:线性遍历链表
function findHook(index) {
let hook = fiber.memoizedState;
for (let i = 0; i < index; i++) {
hook = hook.next;
}
return hook;
}
// React 19:使用索引优化
type Fiber = {
memoizedState: Hook | null,
hookIndex: number, // 新增:当前Hook索引
hookMap: Map<number, Hook> // 新增:Hook索引映射
}
// 2. 批量更新优化
// 多个setState调用会被合并
function Component() {
const [state1, setState1] = useState(0);
const [state2, setState2] = useState(0);
const handleClick = () => {
setState1(1); // 这些更新会被批量处理
setState2(2);
setState1(3);
};
}6.2 编译器优化
javascript
// React 19编译器可以优化Hook调用
// 原始代码
function Component({ items }) {
const [selected, setSelected] = useState(null);
const filtered = items.filter(item => {
return item.id === selected;
});
return <List items={filtered} />;
}
// 编译器自动优化为
function Component({ items }) {
const [selected, setSelected] = useState(null);
// 自动添加useMemo
const filtered = useMemo(() => {
return items.filter(item => {
return item.id === selected;
});
}, [items, selected]);
return <List items={filtered} />;
}注意事项
1. Hook调用顺序不能改变
javascript
// ❌ 错误
function Component({ showExtra }) {
const [count, setCount] = useState(0);
if (showExtra) {
const [extra, setExtra] = useState(''); // 条件Hook!
}
return <div>{count}</div>;
}
// ✅ 正确
function Component({ showExtra }) {
const [count, setCount] = useState(0);
const [extra, setExtra] = useState(''); // 始终调用
return (
<div>
{count}
{showExtra && extra}
</div>
);
}2. 不能在循环中调用Hook
javascript
// ❌ 错误
function Component({ items }) {
return items.map(item => {
const [state] = useState(item); // 循环中的Hook!
return <div>{state}</div>;
});
}
// ✅ 正确
function Component({ items }) {
const [states] = useState(items);
return states.map((state, index) => (
<div key={index}>{state}</div>
));
}3. 只在React函数中调用Hook
javascript
// ❌ 错误
function helper() {
const [state] = useState(0); // 在普通函数中!
return state;
}
// ✅ 正确
function useHelper() { // 自定义Hook
const [state] = useState(0);
return state;
}常见问题
Q1: 为什么不能动态改变Hook数量?
A: 因为Hook依赖位置索引:
javascript
// Hook通过位置对应状态
// 位置0 → count
// 位置1 → name
// 位置2 → effect
// 如果动态改变:
// 第一次:位置0=count, 位置1=name, 位置2=effect
// 第二次:位置0=count, 位置1=effect(name被跳过)
// ❌ 位置1现在是effect,但React认为它是name!Q2: 链表结构会不会影响性能?
A: 影响很小:
javascript
// 1. Hook数量通常不多(<20个)
// 2. 链表遍历是O(n),但n很小
// 3. React有优化:
// - 首次渲染缓存Hook
// - 更新时复用Hook对象
// - React 19的索引优化Q3: 为什么使用链表而不是数组?
A: 链表的优势:
javascript
// 1. 动态构建
// - 不需要预知Hook数量
// - 按需创建节点
// 2. 内存效率
// - 只分配需要的节点
// - 数组可能预分配过多空间
// 3. 简化逻辑
// - 添加节点只需修改next指针
// - 数组需要索引管理
// 4. 与Fiber架构契合
// - Fiber本身就是链表结构
// - Hook链表是Fiber链表的一部分总结
核心概念
- Hook对象:包含memoizedState、queue、next等字段
- 链表结构:Hook通过next指针连接成链表
- Fiber关联:Hook链表挂载在Fiber.memoizedState上
- 顺序依赖:Hook依赖调用顺序维护状态对应关系
- 双缓冲:current树和workInProgress树各有Hook链表
Hook规则的本质
规则:
1. 只在顶层调用Hook
2. 只在React函数中调用Hook
原因:
↓
Hook依赖链表位置索引
↓
顺序改变导致状态错乱
↓
必须保证调用顺序一致链表遍历流程
初始渲染:
构建链表 → Hook1 → Hook2 → Hook3 → null
更新渲染:
遍历current链表 → 创建workInProgress链表 → 复制并更新HookReact 19优化
- Hook索引映射
- 批量更新优化
- 编译器自动优化
- 更高效的遍历算法
理解Hooks链表结构是深入掌握React的关键,它解释了Hook规则的本质原因,也为我们编写更好的React代码提供了理论基础!