React团队对 React 的定位是快速响应。在 React 16 时代React 把 O(n3) 的 Diff 时间复杂度优化到了 O(n)。
一、单线程的 JavaScript 与多线程的浏览器
JavaScript 是单线程的,浏览器是多线程的。
对于多线程的浏览器来说,它除了要处理 JavaScript 线程以外,还需要处理包括事件系统、定时器/延时器、网络请求等各种各样的任务线程,这其中,自然也包括负责处理 DOM 的UI 渲染线程。而 JavaScript 线程是可以操作 DOM 的。
JavaScript 线程和渲染线程必须是互斥的,必须串行,当其中一个线程执行时,另一个线程只能挂起等待。
如果JavaScript 线程长时间地占用了主线程,那么渲染层面的更新就不得不长时间地等待,界面长时间不更新,带给用户的体验就是所谓的“卡顿”。
二、产生“卡顿”这样的原因?
Stack Reconciler 所带来的一个无解的问题,正是JavaScript 对主线程的超时占用问题。Stack Reconciler 是一个同步的递归过程。这个过程的致命性在于它是同步的,不可以被打断。当处理结构相对复杂、体量相对庞大的虚拟 DOM 树时,Stack Reconciler 需要的调和时间会很长,这就意味着 JavaScript 线程将长时间地霸占主线程,进而导致我们上文中所描述的渲染卡顿/卡死、交互长时间无响应等问题。
三、Fiber 是如何解决问题的
Fiber 架构的应用目的是实现“增量渲染”。通俗来说就是把一个渲染任务分解为多个渲染任务,而后将其分散到多个帧里面。不过严格来说,增量渲染其实也只是一种手段,实现增量渲染的目的,是为了实现任务的可中断、可恢复,并给不同的任务赋予不同的优先级,最终达成更加顺滑的用户体验。
1. React15中的架构
在 React 16 之前,React 的渲染和更新阶段依赖的是如下图所示的两层架构:Reconciler 这一层负责对比出新老虚拟 DOM 之间的变化,Renderer 这一层负责将变化的部分应用到视图上,从 Reconciler 到 Renderer 这个过程是严格同步的。
2. React16中的架构
在 React 16 中,为了实现“可中断”和“优先级”,两层架构变成了如下图所示的三层架构:多出来的这层架构,叫作“Scheduler(调度器)”,调度器的作用是调度更新的优先级。
工作流:每个更新任务都会被赋予一个优先级。当更新任务抵达调度器时,高优先级的更新任务(记为 A)会更快地被调度进 Reconciler 层;此时若有新的更新任务(记为 B)抵达调度器,调度器会检查它的优先级,若发现 B 的优先级高于当前任务 A,那么当前处于 Reconciler 层的 A 任务就会被中断,调度器会将 B 任务推入 Reconciler 层。当 B 任务完成渲染后,新一轮的调度开始,之前被中断的 A 任务将会被重新推入 Reconciler 层,继续它的渲染之旅,这便是所谓“可恢复”。
四、Fiber 架构对生命周期的影响
- render 阶段:纯净且没有副作用,可能会被 React 暂停、终止或重新启动。
- pre-commit 阶段:可以读取 DOM。
- commit 阶段:可以使用 DOM,运行副作用,安排更新。
在 render 阶段,React 主要是在内存中做计算,明确 DOM 树的更新点;而 commit 阶段,则负责把 render 阶段生成的更新真正地执行掉。
1、React 15 中从 render 到 commit 的过程:
2、React 16 中,render 到 commit的过程:
可以看出,新老两种架构对 React 生命周期的影响主要在 render 这个阶段,这个影响是通过增加 Scheduler 层和改写 Reconciler 层来实现的。
在 render 阶段,一个庞大的更新任务被分解为了一个个的工作单元,这些工作单元有着不同的优先级,React 可以根据优先级的高低去实现工作单元的打断和恢复。由于 render 阶段的操作对用户来说其实是“不可见”的,所以就算打断再重启,对用户来说也是 0 感知。但是,工作单元(也就是任务)的重启将会伴随着对部分生命周期的重复执行,这些生命周期是:
- componentWillMount
- componentWillUpdate
- shouldComponentUpdate
- componentWillReceiveProps
其中 shouldComponentUpdate 的作用是通过返回 true 或者 false,来帮助我们判断更新的必要性,一般在这个函数中不会进行副作用操作,因此风险不大。而 “componentWill” 开头的三个生命周期,则常年被开发者以各种各样的姿势滥用,所以React16中废弃其它3个生命周期。