React Hooks 原理与规则
理解 Hooks 的链表存储机制和调用规则
问题
React Hooks 是如何存储状态的?为什么不能在条件语句中调用 Hooks?
解答
Hooks 的存储结构
React 使用链表来存储组件的 Hooks 状态。每个组件对应一个 Fiber 节点,Fiber 上有一个 memoizedState 属性指向 Hooks 链表的头节点。
// 简化的 Hook 节点结构
const hook = {
memoizedState: null, // 存储的状态值
next: null // 指向下一个 Hook
};
// Fiber 节点
const fiber = {
memoizedState: hook1, // 指向第一个 Hook
// ...其他属性
};
// 链表结构示意
// fiber.memoizedState -> hook1 -> hook2 -> hook3 -> null
简化实现
// 模拟 React Hooks 的简化实现
let currentFiber = null;
let workInProgressHook = null;
// 模拟 Fiber 节点
function createFiber() {
return {
memoizedState: null, // Hooks 链表头
stateNode: null
};
}
// useState 简化实现
function useState(initialValue) {
let hook;
if (workInProgressHook === null) {
// 第一个 Hook
hook = {
memoizedState: initialValue,
next: null
};
currentFiber.memoizedState = hook;
workInProgressHook = hook;
} else {
// 后续 Hook,挂到链表末尾
hook = {
memoizedState: initialValue,
next: null
};
workInProgressHook.next = hook;
workInProgressHook = hook;
}
const setState = (newValue) => {
hook.memoizedState = newValue;
// 触发重新渲染...
};
return [hook.memoizedState, setState];
}
// 模拟组件渲染
function renderComponent(Component) {
currentFiber = createFiber();
workInProgressHook = null;
// 执行组件函数
Component();
}
更新时的 Hook 读取
// 更新阶段的 useState
let isMount = true; // 是否首次渲染
let currentHook = null;
function useState(initialValue) {
let hook;
if (isMount) {
// 首次渲染:创建新 Hook
hook = {
memoizedState: initialValue,
next: null
};
if (!currentFiber.memoizedState) {
currentFiber.memoizedState = hook;
} else {
workInProgressHook.next = hook;
}
workInProgressHook = hook;
} else {
// 更新渲染:从链表中按顺序读取
hook = currentHook;
currentHook = currentHook.next;
}
const setState = (newValue) => {
hook.memoizedState = newValue;
// 重新渲染时,currentHook 重置为链表头
currentHook = currentFiber.memoizedState;
isMount = false;
renderComponent(Component);
};
return [hook.memoizedState, setState];
}
为什么不能在条件语句中调用
// ❌ 错误示例
function BadComponent({ showName }) {
const [count, setCount] = useState(0);
// 条件调用 Hook
if (showName) {
const [name, setName] = useState('React');
}
const [age, setAge] = useState(18);
return <div>{count}</div>;
}
// 首次渲染 (showName = true)
// 链表: hook1(count) -> hook2(name) -> hook3(age)
// 位置1 位置2 位置3
// 第二次渲染 (showName = false)
// 期望: hook1(count) -> hook3(age)
// 实际读取: 位置1 -> 位置2 -> 位置3
// 结果: age 读到了 name 的值!
// ✅ 正确做法
function GoodComponent({ showName }) {
const [count, setCount] = useState(0);
const [name, setName] = useState('React');
const [age, setAge] = useState(18);
// 条件放在 Hook 外部
const displayName = showName ? name : '';
return <div>{count}</div>;
}
// 每次渲染 Hook 调用顺序一致
// 链表: hook1(count) -> hook2(name) -> hook3(age)
完整示例
// 模拟完整的 Hooks 系统
const React = (function() {
let fiber = { memoizedState: null };
let isMount = true;
let workInProgressHook = null;
function useState(initialValue) {
let hook;
if (isMount) {
hook = { memoizedState: initialValue, next: null };
if (!fiber.memoizedState) {
fiber.memoizedState = hook;
} else {
workInProgressHook.next = hook;
}
workInProgressHook = hook;
} else {
hook = workInProgressHook;
workInProgressHook = workInProgressHook.next;
}
const setState = (action) => {
hook.memoizedState =
typeof action === 'function'
? action(hook.memoizedState)
: action;
};
return [hook.memoizedState, setState];
}
function render(Component) {
workInProgressHook = isMount ? null : fiber.memoizedState;
const result = Component();
isMount = false;
return result;
}
return { useState, render };
})();
// 测试
function Counter() {
const [count, setCount] = React.useState(0);
const [text, setText] = React.useState('hello');
console.log('count:', count, 'text:', text);
return { count, setCount, text, setText };
}
const result1 = React.render(Counter); // count: 0, text: hello
result1.setCount(1);
result1.setText('world');
React.render(Counter); // count: 1, text: world
关键点
- 链表存储:Hooks 以链表形式存储在 Fiber 节点的
memoizedState上 - 顺序依赖:更新时按调用顺序从链表中读取对应的 Hook
- 条件调用问题:条件语句会改变 Hook 调用顺序,导致状态错位
- 规则本质:保证每次渲染 Hook 数量和顺序一致,才能正确匹配状态
- ESLint 插件:使用
eslint-plugin-react-hooks自动检测违规调用
目录