引言
React Hooks 是 React 16.8 引入的特性,它允许我们在函数组件中使用状态和生命周期特性。但 Hooks 背后的原理远比表面看起来复杂——它涉及闭包、链表数据结构以及 React Fiber 架构的深度配合。
Hooks 的设计动机
在 Hooks 出现之前,React 使用类组件和函数组件两种形式。类组件存在几个问题:
- 逻辑复用困难 — HOC 和 render props 模式导致组件嵌套地狱
- 生命周期逻辑分散 — 相关代码被迫拆分到不同生命周期方法中
- this 指向问题 — 初学者容易在事件处理器中遇到 this 绑定问题
useState 的核心原理
// 简化的 useState 实现
let hookIndex = 0;
const hooks = [];
function useState(initialValue) {
const currentIndex = hookIndex;
if (hooks[currentIndex] === undefined) {
hooks[currentIndex] = initialValue;
}
const setState = (newValue) => {
hooks[currentIndex] = newValue;
render(); // 触发重新渲染
};
hookIndex++;
return [hooks[currentIndex], setState];
}
关键点在于:Hooks 的执行顺序必须保持一致。这也是为什么 Hooks 不能放在条件语句或循环中的原因。
闭包陷阱
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
// 这里的 count 始终是 0!
setCount(count + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return <div>{count}</div>;
}
解决方案是使用函数式更新:
setCount(prev => prev + 1);
useEffect 与依赖追踪
useEffect 的依赖数组机制是 React 性能优化的核心:
| 依赖数组 | 执行时机 |
|---|---|
| 无数组 | 每次渲染后都执行 |
| 空数组 [] | 仅在挂载后执行一次 |
| [dep1, dep2] | 依赖变化时执行 |
自定义 Hook 的力量
自定义 Hook 是逻辑复用的最佳方式:
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
总结
React Hooks 的本质是:
- 链表存储 — Hooks 按调用顺序存储在 Fiber 节点的链表中
- 闭包捕获 — 每次渲染都创建新的闭包,捕获当时的 state
- 批量更新 — React 会对多次 setState 进行合并
理解这些原理,才能写出更健壮的 React 组件。