React 从 v15 升级到了 v16 之后重构了整个架构。这一节我们聊聊 v15 为什么不能满足快速响应的理念,以至于被重构。
React15 架构
React 15架构可以分为两层:
- Reconciler(协调器)—— 负责找出发生变化的组件
- Renderer (渲染器)—— 负责将发生变化的组件渲染的界面中
Reconciler(协调器)
在 React 中可以通过 this.setState、this.forceUpdate、ReactDOM.render 等 API 触发更新。
每当有更新发生时,Reconciler 会做如下工作:
- 调用函数组件、或 class 组件的 render 方法,将返回的JSX 转换为虚拟 DOM
- 将虚拟 DOM 和上次更新时的虚拟 DOM 对比
- 通过对比找出本次发生变化的虚拟 DOM
- 通过 Renderer 将变化的虚拟 DOM 渲染到页面上
你可以在这里看到 React 官方对 Reconciler 的解释
Renderer(渲染器)
由于 React 支持跨平台,所以不同平台有不同的 Renderer。我们前端最熟悉的是负责在浏览器环境中渲染的 Renderer —— ReactDOM。
除此之外,还有:
- ReactNative 渲染器,渲染 App 原生组件
- ReactTest 渲染器,渲染出纯 JS 对象用于测试
- ReactArt 渲染器,渲染到 Canvas,SVG 或 VML (IE8)
每次更新发生时,Renderer 接收到 Reconciler 通知,将变化的组件渲染到当前的宿主环境中。
你可以在这里看到 React 官方对 Renderer 的解释。
React 15架构的缺点
在 Reconciler 中,mount 的组件会调用 mountComponent,update 的组件会调用 updateComponent。这两个方法都会递归更新子组件。
递归更新子组件的缺点
由于递归执行,所以更新一旦开始就无法中断了。当层级很深的时候,递归更新的时间就会超过 16ms ,用户交互就会卡顿。
在上个章节中,我们已经提到了 React 的解决办法 —— 用可中断的更新代替同步的更新。那么React15 的架构支持异步更新吗?我们可以看一个例子:
demo code
初始化时 state.count = 1,每次点击按钮 state.count++,列表中 3 个元素的值分别是 1、2、3 乘以 state.count 的结果,用红色标注了更新的步骤:
我们可以看到,Reconciler 和 Renderer 是交替工作的,当第一个 li 在页面上已经变化后,第二个 li 再进入 Reconciler。
由于整个过程都是同步执行的,所以在用户看来所有的 DOM 都是同时更新的。
接下来,我们模拟一下如果中途中断更新会怎么样?
注意 以下是我们模拟中断的情况,实际上 React15 并不会中断进行中的更新
当第一个 li 完成更新是中断更新,即步骤 3 完成后中断更新,此时后面的步骤都还没有执行。
用户原本期望得到 123 变为 246,但实际看到的却是更新不完整的 DOM!(即 223),基于这个原因,React 决定重写整个架构。