mount/update为什么能切换
- 先给一句话答案:
- Hooks 能区分 mount / update,不是靠 Fiber.tag,而是靠「current Fiber 是否存在」
# 一、Hooks Dispatcher 是什么?
# 本质一句话
Hooks Dispatcher = 一组“当前 render 期间,useX 应该执行哪套实现”的函数表
const HooksDispatcherOnMount = {
useState: mountState,
useEffect: mountEffect,
useMemo: mountMemo,
}
const HooksDispatcherOnUpdate = {
useState: updateState,
useEffect: updateEffect,
useMemo: updateMemo,
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
⚠️ useState 本身只是:
export function useState(initialState) {
return ReactCurrentDispatcher.current.useState(initialState)
}
1
2
3
2
3
👉 真正执行哪套逻辑,全看 dispatcher
# 二、Dispatcher 在哪里被切换?
答案:renderWithHooks
# renderWithHooks 的关键判断
ReactCurrentDispatcher.current =
current === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate
1
2
3
4
2
3
4
# current 是谁?
current:上一次 commit 的 FiberworkInProgress:这次 render 的 Fiber
# 结论
| 场景 | current |
|---|---|
| 首次挂载 | null |
| 后续更新 | 指向旧 Fiber |
👉 这是 mount / update 切换的唯一条件
# 三、为什么不能用 Fiber.tag 判断?
你可能会想:
“FunctionComponent 不一直是 FunctionComponent 吗?”
原因是:
- mount / update 是时间维度
- Fiber.tag 是类型维度
同一个 FunctionComponent
第一次 render → mount
第二次 render → update
1
2
3
2
3
👉 tag 根本区分不了
# 四、mount / update 两套逻辑差在哪?
我们用 useState 作为代表。
# 1️⃣ mountState:创建 Hook
function mountState(initialState) {
const hook = mountWorkInProgressHook()
hook.memoizedState = initialState
hook.queue = createUpdateQueue()
const dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber,
hook.queue
)
return [hook.memoizedState, dispatch]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# mount 的核心任务
- 创建 hook 节点
- 建立 hook 链表
- 初始化 state
- 创建 updateQueue
# 2️⃣ updateState:复用 Hook
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]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# update 的核心任务
- 按顺序找到旧 hook
- 克隆到新 Fiber
- 执行 state 更新
# 3️⃣ 两者最本质的差异
| mount | update | |
|---|---|---|
| hook 来源 | 新建 | clone |
| 是否读 queue | ❌ | ✅ |
| 是否消费 update | ❌ | ✅ |
# 五、Hooks 顺序为什么不能变?(根因)
现在你应该能自己推出来了 👇
# render 期间:
useState() // hook #1
useEffect() // hook #2
useMemo() // hook #3
1
2
3
2
3
# update 阶段:
updateWorkInProgressHook()
↕
currentHook.next
1
2
3
2
3
👉 顺序错 = 对错 hook
if (cond) {
useState() // ❌
}
1
2
3
2
3
- mount 时:可能执行
- update 时:可能不执行
- 链表直接错位
# 六、StrictMode 下为什么会“执行两次 mount”?
这是个很好的延伸点。
# React 18 开发模式:
- mount → unmount → mount(模拟)
- 目的是检测副作用
⚠️ 注意:
- Dispatcher 还是 mount
- Fiber 被丢弃重建
- 不会走 update
# 七、和 Vue effect 的根本不同
| Vue3 | React |
|---|---|
| effect 创建一次 | render 可多次 |
| 依赖自动追踪 | 顺序隐式约定 |
| effect ≠ render | render = effect |
# 八、终极一句话总结
- Hooks Dispatcher 能切换,是因为 React 在 renderWithHooks 中,
- 用「current Fiber 是否存在」作为时间锚点,决定 Hook 该“创建”还是“复用”
上次更新: 2026/01/21, 04:36:31