为什么说懒初始化是效率的?
这是 React 内部机制(Hook 的工作原理)决定的。要理解这个原理,我们需要把视角从“我在写代码”转换到“React 实际上在怎么运行我的代码”。
核心原理:React 如何处理 Hook 的链表
在 React 内部,每个组件实例都维护着一个 Hook 链表(Linked List)。
1. 首次渲染(Mount)
当组件第一次被调用时:
- React 创建了一个空的 Hook 链表。
- 遇到
useState(initFn)。 - React 发现这是第一次,所以它必须算出初始值。
- 它看到传入的是一个函数
initFn,于是执行该函数val = initFn()。 - React 将结果
val存入当前 Hook 的节点(MemoizedState)中。 - 返回
[val, dispatch]。
2. 再次渲染(Re-render)
当组件因为 props 变了或 setState 被调用而再次执行时:
- React 复用之前的 Hook 链表。
- 再次遇到
useState(initFn)。 - React 查看当前 Hook 节点,发现:“噢,这里已经存过状态了!”
- React 直接忽略传入的参数(不管是值还是函数
initFn)。它根本不会去调用initFn。 - 它直接从链表中取出之前存好的(或者是最新更新过的)
state。 - 返回
[currentState, dispatch]。
用伪代码模拟 React 内部行为
我们可以写一段简化版的 JS 代码来模拟 React useState 的内部逻辑,你会立刻明白为什么函数只跑一次:
// React 内部维护的全局变量(简化版)
let hookState = []; // 存放每个 hook 的状态
let hookIndex = 0; // 当前执行到第几个 hook
function useState(initialState) {
const currentIndex = hookIndex;
hookIndex++; //哪怕后面不执行,索引也要加1
// 关键判断:即便你传入了函数,只要状态已存在,我就不看它
if (hookState[currentIndex] !== undefined) {
// 【Re-render 路径】
// 直接返回之前存好的值,完全不管 initialState 参数是什么
return [hookState[currentIndex], setState];
}
// 【Mount 路径 - 只有第一次会进这里】
let value;
if (typeof initialState === "function") {
// 只有这里才会调用你的函数!
value = initialState();
} else {
value = initialState;
}
hookState[currentIndex] = value;
function setState(newVal) {
/* ...触发重渲染... */
}
return [value, setState];
}
总结
原理非常简单粗暴:
- Mount 阶段:React 需要初始值 -> 不得不执行你的函数。
- Update 阶段:React 已经有值了 -> 直接丢弃你传入的参数(参数虽然被传入了函数,但 React 内部逻辑里的
if判断让它根本没机会被调用)。
这就是为什么惰性初始化能节省性能:虽然函数定义本身在每次渲染时都会重新创建(因为在组件函数体内),但那个函数体内部的代码永远不会被执行。
