- 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
: HooksDispatcherOnUpdatecurrent 是谁?
current:上一次 commit 的 FiberworkInProgress:这次 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️⃣ 两者最本质的差异
| mount | update | |
|---|---|---|
| hook 来源 | 新建 | clone |
| 是否读 queue | ❌ | ✅ |
| 是否消费 update | ❌ | ✅ |
五、Hooks 顺序为什么不能变?(根因)
现在你应该能自己推出来了 👇
render 期间:
ts
useState() // hook #1
useEffect() // hook #2
useMemo() // hook #3update 阶段:
ts
updateWorkInProgressHook()
↕
currentHook.next👉 顺序错 = 对错 hook
ts
if (cond) {
useState() // ❌
}- 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 该“创建”还是“复用”