Skip to content

结论

  • 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 useMemo

render #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 可能拿到的是 2
  • setB 操作的是错误的 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 风格”的交易。