第1部分 — Stack Reconciler时代
Source: Dev.to
高层次流程
深入调和过程
1. 触发器:this.setState()
this.setState() 是“启动”信号。React 会立即开始处理,找出该组件及其整个子树发生了哪些变化。
2. 递归“深入”
React 调用组件的 render() 方法。由于组件是嵌套的,这会触发递归链:
- 如果组件 A 有子组件 B,React 会先调用
A.render(),随后立即深入调用B.render()。 - 每一次函数调用都会被压入 JavaScript 调用栈。
关键约束: JavaScript 是单线程的。当调用栈被 React 的递归占用时,浏览器无法处理用户输入(如滚动或点击“取消”按钮)。主线程会被“劫持”,直到栈完全清空。
3. 生成虚拟 DOM
React 创建一个轻量级、驻留在内存中的 JavaScript 对象,表示此时 UI 的“期望”状态——即虚拟 DOM。
4. 同步 Diff 与即时 DOM 打补丁
Diff(找出差异)和 patch(更新屏幕)是同步进行的。当算法遍历树并发现差异——例如 变为——它会立即调用 document.setAttribute 或 appendChild,立刻更新真实 DOM。
因为 React 在计算剩余树结构的同时就已经修改了真实 DOM,这个过程无法被暂停。不能中途停止,因为 UI 已经开始改变。
性能瓶颈
卡顿与丢帧
要保持流畅的 60 fps,浏览器大约有 16.6 ms 来绘制一帧。如果调和过程耗时 30 ms,浏览器就会跳过一帧,导致可见的卡顿或“卡顿感”。
延迟的用户交互
如果用户在 React 正忙于调和一个大型列表时输入内容,按键的显示会被延迟,直到整个树的计算完成。
领悟
对复杂、高交互性应用来说,同步的 diff 与 patch 是行不通的。为了解决这个问题,确定了两个目标:
- 将“数学”(diff)与“写入”(patch)解耦。
- 实现暂停功能: 当用户执行更重要的操作时,能够中止“数学”计算。
这一领悟为 React 历史上最宏大的重写——Fiber——奠定了基础。在第 2 部分,我们将深入探讨 Fiber 如何彻底重构 React 的内部机制,让网页体验前所未有的流畅。