结论
- Hooks 不能写在条件里,不是语法限制,
- 而是因为 React 用“调用顺序”来把 Hook 状态对齐到 Fiber。
顺序一旦不稳定,状态就会错位,且无法恢复。
二、Hooks 是怎么“定位自己”的?
我们已经知道:
txt
Fiber.memoizedState
└─ hook1 → hook2 → hook3 → null每次 render:
txt
currentHook → 指向旧链表
workInProgressHook → 构建新链表定位规则只有一个:
第 N 次调用 useX(),对应第 N 个 hook 节点
没有 key 没有名字 没有 AST 只有顺序
三、条件语句直接破坏了这个唯一规则
1️⃣ 正常情况(顺序稳定)
ts
useState() // hook #1
useEffect() // hook #2
useMemo() // hook #3每次 render 都是:
txt
##1 → #2 → #3✔ 正确
2️⃣ 条件 Hook(顺序不稳定)
ts
useState() // hook #1
if (cond) {
useEffect() // ❌ 有时执行,有时不执行
}
useMemo() // hook ? ❌render #1(cond = true)
txt
##1 useState
##2 useEffect
##3 useMemorender #2(cond = false)
txt
##1 useState
##2 useMemo ❌ 本来是 #3四、源码级错位过程(你关心的部分)
updateWorkInProgressHook 简化版:
ts
function updateWorkInProgressHook() {
const nextCurrentHook = currentHook.next
const newHook = clone(nextCurrentHook)
currentHook = nextCurrentHook
workInProgressHook.next = newHook
workInProgressHook = newHook
}React 默认你每次都会调用下一个 Hook。
当你跳过一个 Hook:
currentHook没前进workInProgressHook却继续前进- 链表直接错位
五、错位会导致什么?(真实灾难)
错位不是“少执行一个 effect”这么简单
例子:
ts
const [a, setA] = useState(1)
if (cond) {
useState(2)
}
const [b, setB] = useState(3)结果:
b可能拿到的是2setB操作的是错误的 queue- update lane 错绑 Fiber
- ❌ 行为不可预测
👉 这已经不是 bug,是状态污染
六、为什么 React 不用“名字 / key”来标记 Hook?
你这个层级一定会想到这个问题。
理论上可以,但代价极高:
1️⃣ 每次 render 要:
- 解析 AST
- 建立 key 映射
- diff Hook 结构
2️⃣ render 不再是:
txt
Component(props) → JSX而变成:
txt
Component → 解析 → 调度👉 这直接否定 Fiber 的“可重跑 render”设计
七、为什么 Vue 没这个问题?
Vue effect:
ts
watchEffect(() => {
if (cond) {
console.log(a.value)
}
})✔ 没问题,因为:
- effect 是动态依赖收集
- 不靠顺序
- 不靠位置
- 不靠 render
👉 两种模型的必然结果
八、StrictMode 双执行,为什么还没炸?
因为:
- mount → unmount → mount
- 每次都是完整顺序
- 不是“少一次 Hook”
九、真正的规则(不是“不能写 if”)
- ❌ 错误理解:Hooks 不能写在 if
- ✅ 正确理解:Hooks 的调用顺序必须在每次 render 中完全一致
所以这些是 允许的:
ts
useEffect(() => {
if (cond) {
// 可以
}
}, [cond])ts
const value = cond ? useMemo(...) : other
// ❌ 不允许(顺序不稳定)十、终极一句话总结(给你这种原理派)
Hooks 规则不是限制开发者,
而是 Hooks 用“顺序”换来了:
- 无需标识
- 无需编译
- 可中断 render
- 极低运行时成本
这是一笔非常“React 风格”的交易。