一次
setState,是怎么绕一大圈,又精确“回到”对应 Fiber 的?
setState → dispatchAction → lane → Fiber 这条链完整拆开。
总览(先把全链路记住)
txt
setState
→ dispatchSetState
→ requestUpdateLane
→ enqueueUpdate (hook.queue)
→ scheduleUpdateOnFiber
→ markRootUpdated
→ renderRoot
→ beginWork(fiber)- 👉 关键点:Fiber 在 setState 时就已经被“捕获”了
一、setState 的“魔法”:它一开始就绑定了 Fiber
你先回忆 useState 返回的 setState:
ts
const [state, setState] = useState(0)真相是:👇
ts
const dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber,
hook.queue
)- 👉 setState 是一个闭包
- 👉 它早就记住了:
fiberupdateQueue
⚠️ 重点结论:
setState 不需要“查 Fiber”,因为它从一开始就带着 Fiber
二、dispatchSetState:更新的真正入口
ts
function dispatchSetState(fiber, queue, action) {
const lane = requestUpdateLane(fiber)
const update = {
lane,
action,
next: null,
}
enqueueUpdate(queue, update)
scheduleUpdateOnFiber(fiber, lane)
}这一步发生了三件关键事:
- 1️⃣ 决定优先级(lane)
- 2️⃣ 把 update 放进 hook.queue
- 3️⃣ 通知调度系统:这个 Fiber 要更新
三、lane 是什么?(对 Vue scheduler 的升级)
Vue:
txt
trigger → jobQueueReact:
txt
update → lane → root.pendingLaneslane 本质
lane = 用 bit 表示的优先级通道
ts
SyncLane = 0b0001
InputLane = 0b0010
TransitionLane = 0b0100
IdleLane = 0b1000- 👉 多个 lane 可同时存在
- 👉 Scheduler 每次选最高优先级
requestUpdateLane 做了什么?
ts
function requestUpdateLane(fiber) {
if (isSync) return SyncLane
if (isTransition) return TransitionLane
return DefaultLane
}- 点击 → Sync
- startTransition → Transition
- 普通 setState → Default
四、enqueueUpdate:更新“挂”在哪里?
对 Hooks(useState)
ts
queue.pending = update更新结构:
txt
Fiber
└─ memoizedState
└─ hook
└─ queue
└─ pending → update- 👉 update 并不在 Fiber 本身
- 👉 在 hook.queue 上
五、scheduleUpdateOnFiber:真正“回到 Fiber”
ts
function scheduleUpdateOnFiber(fiber, lane) {
const root = markUpdateLaneFromFiberToRoot(fiber, lane)
ensureRootIsScheduled(root)
}markUpdateLaneFromFiberToRoot
txt
fiber
↑ return
parent
↑ return
...
root沿着 fiber.return 一路向上:
ts
parent.lanes |= lane
root.pendingLanes |= lane- 👉 这一步,Fiber 成功“通知”了整棵树
六、为什么一定要“回到 root”?
因为:
- 调度是 root 级别的
- render 是从 root 开始
- Scheduler 不关心某个 Fiber
txt
root.pendingLanes != 0
→ root 需要 render七、Scheduler 什么时候真正 render?
ts
performConcurrentWorkOnRoot(root)1️⃣ 选最高优先级 lane
2️⃣ renderRoot(lane)
3️⃣ workLoop
4️⃣ beginWork(root.child)
👉 最终又回到最初那个 Fiber
八、render 阶段:update 被消费
当 render 到对应 Fiber:
ts
updateFunctionComponent
→ renderWithHooks
→ updateStatets
const pending = hook.queue.pending- 消费 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”。