Skip to main content

第 21 章:一次 setState 的完整旅程

第 4 章的全局地图说"后面每一章都是某个路口的特写"。到这一章,特写都拍完了,我们把地图完整走一遍:点一次按钮,setCount 从被调用到页面更新,每一站对应哪个函数、哪一章

就用第 4 章那个 Counter:

function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}

先记住这一句:一次 setState 是五个系统的接力:dispatch(造 update)→ enqueue(入队)→ schedule(调度)→ render(算新树)→ commit(落 DOM)。你已经分别见过它们,本章把它们串成一条线。

一图流:setCount 的完整旅程

第 1 站:dispatch(造出 update)

点按钮,onClick 触发,setCount((c) => c + 1) 执行。它走进 dispatchSetStateReactFiberHooks.js:3599,第 13 章主角),做两件事:

  1. requestUpdateLane 要一个 lane(优先级,第 22 章)。
  2. 造一个 update 对象 {lane, action, eagerState, next},action 就是你传的函数。

如果队列是空的,还会急切求值:当场用 reducer 算一遍,同值直接 bailout 连渲染都省了(第 13 章)。

第 2 站:enqueue(塞进 hook 的队列)

update 交给 enqueueConcurrentHookUpdateReactFiberConcurrentUpdates.js:114),塞进这个 hook 的 queue.pending 环链表。注意 setState 到此为止只做了"记账",还没算任何东西。

第 3 站:schedule(通知"有事要做")

scheduleUpdateOnFiberReactFiberWorkLoop.js:916)把这次更新标记到 fiber 的 lanes 上,并把 lane 冒泡到所有祖先的 childLanes(第 6 章那个"预约登记",第 4 章附录的两种冒泡之 ①)。然后根据优先级决定:直接同步渲染,还是交给 Scheduler(第 5 章)排队。

第 4 站:render(算新树)

Scheduler 回调 performWorkOnRoot1066),prepareFreshStack1932)复制出 workInProgress 树,工作循环开跑(performUnitOfWork2990)。

遍历到 Counter 这个 fiber,beginWork 按 tag 分派(第 6 章),函数组件走 renderWithHooksuseState 的 update 版本 updateReducer1294)把 baseQueue 和 pending 合并,逐个 action 跑 reducer,算出新 count(第 13 章)。

新 JSX 出来后 reconcileChildren(第 7 章)diff 出子 fiber 的变化,completeWork(第 8 章)向上冒泡 flags。整棵树走完,得到 finishedWork。此时页面还纹丝未动

第 5 站:commit(落 DOM)

commitRoot3416)进入第 17 章的三阶段:before mutation 读旧树 → mutation 改 DOM(消费第 7、8 章打的 Placement/Update 标记)→ layout 跑 layoutEffect。改完 current 换到新树,useEffect 异步收尾(第 20 章)。

第 6 站:收尾

按钮上的数字变了。下一次点击,从第 1 站再来一遍。

这一章没有新知识,它验证了一件事:前面 20 章的每一块,最终都在一次点击里各司其职。如果哪一站你觉得陌生,往回翻对应章节即可。

动手实验

  1. 全程断点:依次在 dispatchSetState(3599)、scheduleUpdateOnFiber(916)、updateReducer(1294)、commitRoot(3416)打四个断点,点一次按钮,看它们按顺序各出现一次
  2. 观察 render 不动 DOM:在 updateReducercommitRoot 之间,给 document.body.innerHTML 打日志,确认 render 结束前 DOM 没变。
  3. 看 lane 冒泡:在 scheduleUpdateOnFiber 打断点,看它如何沿 return 链给祖先的 childLanes 置位。

小结

  • 一次 setState = dispatch → enqueue → schedule → render → commit 五站接力。
  • 每站对应一个核心函数:dispatchSetState(3599) / enqueueConcurrentHookUpdate(114) / scheduleUpdateOnFiber(916) / performWorkOnRoot(1066) + updateReducer(1294) / commitRoot(3416)。
  • render 算新树不动 DOM,commit 落 DOM,两段的分界是全书主线。
  • 下一步:第 5 站里那个 lane 到底是什么?第 22 章看优先级模型。