Skip to content

一次 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 是一个闭包
  • 👉 它早就记住了:
  • fiber
  • updateQueue

⚠️ 重点结论:

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 → jobQueue

React:

txt
update → lane → root.pendingLanes
lane 本质

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
 → updateState
ts
const pending = hook.queue.pending
  • 消费 update
  • 计算新 state
  • 写入 hook.memoizedState
  • 👉 闭环完成

九、为什么 React 更新“绕一大圈”?

你可能会觉得:

“Vue trigger effect 多直接啊?”

原因是:

React 必须支持:

  • 并发
  • 中断
  • 优先级
  • 丢弃 render
  • 👉 所以 update 必须是“可延迟消费的描述”,而不是立即执行

十、Vue effect vs React update(对照)

VueReact
setter 触发dispatchAction
dep 定位闭包记住 Fiber
schedulerlane
effect 执行render 重跑
精确更新可中断更新

十一、总结

setState 能“回到 Fiber”,不是因为 React 很聪明, 而是因为 setState 从诞生那一刻起,就已经“记住了它来自哪个 Fiber”。