在19年React发布了16版本,这个版本最大的特点就是架构发生了改变。
由Stack架构更新为Fiber架构。来看一下两种架构的构成。
Stack架构
- Reconciler(协调器):vdom 的实现,根据自变量的变化计算出 UI 的变化
- Renderer(渲染器):负责将 UI 的变化渲染到宿主环境
协调器负责生成vDom,并进行diff。渲染器将vDom的更新渲染浏览器。
Stack架构的在每次更新时都会递归遍历vDom树,每一次递归都会开启一个函数栈
所以叫Stack(栈)架构。
React官方还给了两种架构区别的例子
从例子中可以看到Stack架构很卡,而Fiber却很丝滑。造成卡顿的原因还是由于浏览器的事件循环机制造成的,因为JS引擎只有一个主线程,同步代码,微任务,宏任务,dom操作。他们最终都会在主线程上被执行。比如setTimeout,会被先推入宏任务线程上,但是当timeout结束时,还是会将callback推入到主线程。
可以理解为主线程被的执行栈被塞满了,要一个个的排队执行。这也是定时器和计时器并不是100%准确的原因。
Fiber架构
- Scheduler(调度器):调度任务的优先级,高优先级的任务会优先进入到 Reconciler
- Reconciler(协调器):vdom 的实现,根据自变量的变化计算出 UI 的变化
- Renderer(渲染器):负责将 UI 的变化渲染到宿主环境
同Stack架构相比多了一个调度器。在细节上v16还增加了Fiber Node,这是一种新的虚拟Dom。原来的虚拟Dom是树形结构,而Fiber Node则是链表结构。
虚拟Dom树
Fiber 链表
使用链表的结构结构最大的好处就是可以被打断。因为链表上的每个节点都可以被当做head。而且双向链表可以更方便的处理后续的complateWork
工作。
我们知道屏幕的PFS大于等于60时我们视觉上感觉不到卡顿,也就是1000ms / 60 ≈ 160; 每0.16秒渲染一次。
浏览器的渲染流水线大概是
graph TD
requestAnimationFrame --> 解析HTML
--> 计算样式 --> 布局 --> 更新图层 -->
绘制像素点 --> requestIdleCallback
调度器会给每个任务打上tag,不同的任务有着不同的优先级。高优先级的任务会先进入协调器。
协调器就是根据requestIdleCallback判断在这一帧的时间切片是否还有足够的时间,如果时间不够了就去打断js的执行,在下一帧再接着去计算。
function delay(duration) {
const start = Date.now();
while (Date.now() - start < duration) {}
}
const taskList = []; // 存放任务的队列
// 推入任务
for (let i = 1; i <= 10; i++) {
taskList.push(() => {
delay(10);
console.log(`执行任务${i}`);
});
}
function callback(IdleDeadline) {
// 执行任务
console.log(
"当前帧绘制完毕后所剩余的时间:",
IdleDeadline.timeRemaining()
);
while (IdleDeadline.timeRemaining() > 0 && taskList.length) {
// 还有剩余时间,并且任务列表还有任务
const task = taskList.shift();
task();
}
// 退出上面的 while 后,有一种情况是当前帧的时间不够了,但是任务列表中还有剩余任务
if (taskList.length) {
// 那么我们就在下一帧空闲时间再继续执行任务
window.requestIdleCallback(callback);
}
}
window.requestIdleCallback(callback);
渲染器将diff后的fiber一次性渲染,渲染是不可打断的。