React 更新系统的“回路闭合点”
一次
setState,是怎么绕一大圈,又精确“回到”对应 Fiber 的?
setState → dispatchAction → lane → Fiber 这条链完整拆开。
# 一句话总览(先把全链路记住)
setState
→ dispatchSetState
→ requestUpdateLane
→ enqueueUpdate (hook.queue)
→ scheduleUpdateOnFiber
→ markRootUpdated
→ renderRoot
→ beginWork(fiber)
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 👉 关键点:Fiber 在 setState 时就已经被“捕获”了
# 一、setState 的“魔法”:它一开始就绑定了 Fiber
你先回忆 useState 返回的 setState:
const [state, setState] = useState(0)
1
# 真相是:👇
const dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber,
hook.queue
)
1
2
3
4
5
2
3
4
5
- 👉 setState 是一个闭包
- 👉 它早就记住了:
fiberupdateQueue
⚠️ 重点结论:
setState 不需要“查 Fiber”,因为它从一开始就带着 Fiber
# 二、dispatchSetState:更新的真正入口
function dispatchSetState(fiber, queue, action) {
const lane = requestUpdateLane(fiber)
const update = {
lane,
action,
next: null,
}
enqueueUpdate(queue, update)
scheduleUpdateOnFiber(fiber, lane)
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
这一步发生了三件关键事:
- 1️⃣ 决定优先级(lane)
- 2️⃣ 把 update 放进 hook.queue
- 3️⃣ 通知调度系统:这个 Fiber 要更新
# 三、lane 是什么?(对 Vue scheduler 的升级)
# Vue:
trigger → jobQueue
1
# React:
update → lane → root.pendingLanes
1
# lane 本质
lane = 用 bit 表示的优先级通道
SyncLane = 0b0001
InputLane = 0b0010
TransitionLane = 0b0100
IdleLane = 0b1000
1
2
3
4
2
3
4
- 👉 多个 lane 可同时存在
- 👉 Scheduler 每次选最高优先级
# requestUpdateLane 做了什么?
function requestUpdateLane(fiber) {
if (isSync) return SyncLane
if (isTransition) return TransitionLane
return DefaultLane
}
1
2
3
4
5
2
3
4
5
- 点击 → Sync
- startTransition → Transition
- 普通 setState → Default
# 四、enqueueUpdate:更新“挂”在哪里?
# 对 Hooks(useState)
queue.pending = update
1
更新结构:
Fiber
└─ memoizedState
└─ hook
└─ queue
└─ pending → update
1
2
3
4
5
2
3
4
5
- 👉 update 并不在 Fiber 本身
- 👉 在 hook.queue 上
# 五、scheduleUpdateOnFiber:真正“回到 Fiber”
function scheduleUpdateOnFiber(fiber, lane) {
const root = markUpdateLaneFromFiberToRoot(fiber, lane)
ensureRootIsScheduled(root)
}
1
2
3
4
2
3
4
# markUpdateLaneFromFiberToRoot
fiber
↑ return
parent
↑ return
...
root
1
2
3
4
5
6
2
3
4
5
6
沿着 fiber.return 一路向上:
parent.lanes |= lane
root.pendingLanes |= lane
1
2
2
- 👉 这一步,Fiber 成功“通知”了整棵树
# 六、为什么一定要“回到 root”?
因为:
- 调度是 root 级别的
- render 是从 root 开始
- Scheduler 不关心某个 Fiber
root.pendingLanes != 0
→ root 需要 render
1
2
2
# 七、Scheduler 什么时候真正 render?
performConcurrentWorkOnRoot(root)
1
1️⃣ 选最高优先级 lane
2️⃣ renderRoot(lane)
3️⃣ workLoop
4️⃣ beginWork(root.child)
👉 最终又回到最初那个 Fiber
# 八、render 阶段:update 被消费
当 render 到对应 Fiber:
updateFunctionComponent
→ renderWithHooks
→ updateState
1
2
3
2
3
const pending = hook.queue.pending
1
- 消费 update
- 计算新 state
- 写入
hook.memoizedState
- 👉 闭环完成
# 九、为什么 React 更新“绕一大圈”?
你可能会觉得:
“Vue trigger effect 多直接啊?”
原因是:
# React 必须支持:
- 并发
- 中断
- 优先级
- 丢弃 render
- 👉 所以 update 必须是“可延迟消费的描述”,而不是立即执行
# 十、Vue effect vs React update(终极对照)
| Vue | React |
|---|---|
| setter 触发 | dispatchAction |
| dep 定位 | 闭包记住 Fiber |
| scheduler | lane |
| effect 执行 | render 重跑 |
| 精确更新 | 可中断更新 |
# 十一、终极一句话总结(这条非常重要)
setState 能“回到 Fiber”,不是因为 React 很聪明, 而是因为 setState 从诞生那一刻起,就已经“记住了它来自哪个 Fiber”。
上次更新: 2026/01/21, 04:36:31