Skip to content
  • Hooks 能区分 mount / update,不是靠 Fiber.tag,而是靠「current Fiber 是否存在」

一、Hooks Dispatcher 是什么?

本质一句话

Hooks Dispatcher = 一组“当前 render 期间,useX 应该执行哪套实现”的函数表

ts
const HooksDispatcherOnMount = {
  useState: mountState,
  useEffect: mountEffect,
  useMemo: mountMemo,
}

const HooksDispatcherOnUpdate = {
  useState: updateState,
  useEffect: updateEffect,
  useMemo: updateMemo,
}

⚠️ useState 本身只是:

ts
export function useState(initialState) {
  return ReactCurrentDispatcher.current.useState(initialState)
}

👉 真正执行哪套逻辑,全看 dispatcher

二、Dispatcher 在哪里被切换?

答案:renderWithHooks

renderWithHooks 的关键判断

ts
ReactCurrentDispatcher.current =
  current === null
    ? HooksDispatcherOnMount
    : HooksDispatcherOnUpdate

current 是谁?

  • current上一次 commit 的 Fiber
  • workInProgress这次 render 的 Fiber

结论

场景current
首次挂载null
后续更新指向旧 Fiber

👉 这是 mount / update 切换的唯一条件

三、为什么不能用 Fiber.tag 判断?

你可能会想:

“FunctionComponent 不一直是 FunctionComponent 吗?”

原因是:

  • mount / update 是时间维度
  • Fiber.tag 是类型维度
txt
同一个 FunctionComponent
第一次 render  → mount
第二次 render → update

👉 tag 根本区分不了

四、mount / update 两套逻辑差在哪?

我们用 useState 作为代表。

1️⃣ mountState:创建 Hook

ts
function mountState(initialState) {
  const hook = mountWorkInProgressHook()

  hook.memoizedState = initialState
  hook.queue = createUpdateQueue()

  const dispatch = dispatchSetState.bind(
    null,
    currentlyRenderingFiber,
    hook.queue
  )

  return [hook.memoizedState, dispatch]
}

mount 的核心任务

  • 创建 hook 节点
  • 建立 hook 链表
  • 初始化 state
  • 创建 updateQueue

2️⃣ updateState:复用 Hook

ts
function updateState() {
  const hook = updateWorkInProgressHook()
  const queue = hook.queue

  let newState = hook.memoizedState

  // 处理 updateQueue
  const pending = queue.pending
  if (pending !== null) {
    // 计算新 state
  }

  hook.memoizedState = newState
  return [newState, queue.dispatch]
}

update 的核心任务

  • 按顺序找到旧 hook
  • 克隆到新 Fiber
  • 执行 state 更新

3️⃣ 两者最本质的差异

mountupdate
hook 来源新建clone
是否读 queue
是否消费 update

五、Hooks 顺序为什么不能变?(根因)

现在你应该能自己推出来了 👇

render 期间:

ts
useState()   // hook #1
useEffect()  // hook #2
useMemo()    // hook #3

update 阶段:

ts
updateWorkInProgressHook()

currentHook.next

👉 顺序错 = 对错 hook

ts
if (cond) {
  useState() // ❌
}
  • mount 时:可能执行
  • update 时:可能不执行
  • 链表直接错位

六、StrictMode 下为什么会“执行两次 mount”?

这是个很好的延伸点。

React 18 开发模式:

  • mount → unmount → mount(模拟)
  • 目的是检测副作用

⚠️ 注意:

  • Dispatcher 还是 mount
  • Fiber 被丢弃重建
  • 不会走 update

七、和 Vue effect 的根本不同

Vue3React
effect 创建一次render 可多次
依赖自动追踪顺序隐式约定
effect ≠ renderrender = effect

八、一句话总结

  • Hooks Dispatcher 能切换,是因为 React 在 renderWithHooks 中,
  • 用「current Fiber 是否存在」作为时间锚点,决定 Hook 该“创建”还是“复用”