React新老架构

# React15架构
React15架构可分两层:
- Reconciler(协调器):负责找出变化的组件
- Renderer(渲染器):负责将变化的组件渲染到页面上
# Reconciler(协调器)
在React中可通过this.setState、this.forceUpdate、ReactDOM.render等API触发更新。
每当有更新发生时,Reconciler会做如下工作:
- 调用函数组件或class组件的render方法,将返回的JSX转化为虚拟DOM
- 将虚拟DOM和上次更新时的虚拟DOM对比
- 通过对比找出本次更新中变化的虚拟DOM
- 通知Renderer将变化的虚拟DOM渲染到页面上 可在这里 (opens new window)看到React官方对Reconciler的解释
# Renderer(渲染器)
由于React支持跨平台,所以不同平台有不同的Renderer。我们最熟悉的是负责在浏览器环境渲染的Renderer——ReactDOM (opens new window) 除此之外,还有:
- ReactNative (opens new window)渲染器,渲染App原生组件
- ReactTest (opens new window)渲染器,渲染出纯js对象用于测试
- ReactArt (opens new window)渲染器,渲染到Canvas,SVG或VML(IE8)
可在这里 (opens new window)看到React官方对Renderer的解释
# React15架构的缺点
在Reconciler中,mount的组件会调用mountComponent (opens new window),
update的组件会调用updateComponent (opens new window)。
这两个方法都会递归更新子组件。
递归更新的缺点:一旦开始,中途无法中断。当层级很深时,递归更新时间超过16ms,用户交互就会卡顿。
那么React15的架构支持异步更新吗?我们来看一个例子:
初始化时state.count=1,每次点击按钮state.count++
列表中3个元素的值分别为1,2,3乘以state.count的结果
用红色标注了更新的步骤
我们看到,Reconciler和Renderer是交替工作的。
由于整个过程都是同步的,所以在用户看来所有DOM是同时更新的。
我们模拟下,如果中途中断更新会怎么样(React15并不会中断进行中的更新)?
可看到,当第一个li完成更新时中断更新,即步骤3完成后中断更新,此时后面的步骤都还未执行。
用户本来期望123变成246,。实际却看见更新不完全的DOM(即223)
为此,React重写了整个架构。
# React16架构
React16架构可分三层:
- Scheduler(调度器)——调度任务优先级,高优先任务优先进入Reconciler
- Reconciler(协调器):负责找出变化的组件
- Renderer(渲染器):负责将变化的组件渲染到页面上
# Scheduler(调度器)
既然我们以浏览器是否有剩余时间作为任务中断的标准,那么我们需要一种机制:当浏览器有剩余时间时通知我们。 其实部分浏览器已实现这个API,就是requestldleCallback (opens new window)。 但由于以下因素,React放弃使用:
- 浏览器兼容性
- 触发频率不稳定,受多因素影响。如当浏览器切换tab后,之前tab注册的
requestldleCallback触发频率会变很低
基于以上原因,React实现了功能更完备的requestIdleCallback polyfill,这就是Scheduler。
除了在空闲时触发回调的功能外,Scheduler还提供了多种调度优先级供任务设置。
Scheduler (opens new window)是独立于React的库
# Reconciler(协调器)
React15中Reconciler是递归处理虚拟DOM的。让我们看看React16的Reconciler (opens new window)
可看到,更新工作从递归变成了可中断的循环过程。每次循环都会调用shouldYield判断当前是否有剩余时间
/** @noinline */
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
workInProgress = performUnitOfWork(workInProgress);
}
}
2
3
4
5
6
7
那么React16是如何解决中断更新时DOM渲染不完全的问题呢? 在React16中,Reconciler与Renderer不再是交替工作。 当Scheduler将任务交给Reconciler后,Reconciler会为变化的虚拟DOM打上代表增/删/更新的标记,类似这样:
export const Placement = /* */ 0b0000000000010;
export const Update = /* */ 0b0000000000100;
export const PlacementAndUpdate = /* */ 0b0000000000110;
export const Deletion = /* */ 0b0000000001000;
2
3
4
整个Scheduler与Reconciler的工作都在内存中进行。只有当所有组件都完成Reconciler的工作,才会统一交给Renderer。
这里 (opens new window)是React官方对React1新Reconciler的解释
可看到,Reconciler内部采用了Fiber架构
# Renderer(渲染器)
Renderer根据Reconciler为虚拟DOM打的标记,同步执行对应的DOM操作。
之前的Demo在React16架构中整个更新流程为:
其中红框中的步骤随时可能由于以下原因被中断:
- 有其他更高优任务需先更新
- 当前帧无剩余时间
由于红框中的工作在内存中进行,不会更新页面上的DOM,所以即使反复中断,用户也看不到更新不完全的DOM。
由于Scheduler和Reconciler都是平台无关的,所以React为他们单独发了一个包react-reconciler (opens new window),我们可用这个包自己实现一个ReactDOM,
具体见参考资料 (opens new window)