React Hooks 原理:useState 和 useEffect 是如何工作的?什么是闭包陷阱?
请说明 React useState 和 useEffect 的内部实现原理,以及常见的闭包陷阱问题。
回答
苦行僧
useState 原理:
- 每个 Fiber 节点维护一个 Hooks 链表(
memoizedState) - 首次渲染时,按调用的顺序将每个 Hook 的 state 和 dispatcher 存入链表
- 更新时,按相同顺序取出对应的 Hook 值
setState会触发当前组件的重新渲染- 关键保证:Hook 调用顺序必须稳定,不能放在条件/循环中
// 简化原理
let currentHook = 0;
const hooks = [];
function useState(initial) {
const hookIndex = currentHook;
hooks[hookIndex] = hooks[hookIndex] ?? initial;
const setState = (newVal) => {
hooks[hookIndex] = newVal;
scheduleUpdate(); // 触发重新渲染
};
currentHook++;
return [hooks[hookIndex], setState];
}
useEffect 原理:
- useEffect 的回调和依赖数组也存入 Hook 链表
- 每次渲染时,对比依赖是否变化(
Object.is比较) - 依赖变化则标记副作用,在 commit 阶段后异步执行
- 清除函数在重新执行前或组件卸载时调用
闭包陷阱:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1); // ❌ count 永远是初始值 0
}, 1000);
return () => clearInterval(timer);
}, []); // 依赖为空,闭包捕获了初始 count
// 修复:使用函数式更新
// setCount(prev => prev + 1);
}
解决方案:
- 函数式更新:
setCount(c => c + 1) - 正确填写依赖数组
useRef保存可变引用useCallback/useMemo管理引用相等性