概述
React 16 之前的版本比对更新 VirtualDOM 的过程是采纳循环加递归实现的,这种比对形式有一个问题,就是一旦工作开始进行就无奈中断,如果利用中组件数量宏大,主线程被长期占用,直到整棵 VirtualDOM 树比对更新实现之后主线程能力被开释,主线程能力执行其余工作。这就会导致一些用户交互,动画等工作无奈立刻失去执行,页面就会产生卡顿, 十分的影响用户体验。
Fiber 就是 React 提出的用于解决页面卡顿的计划,蕴含如下三个方面:
- 利用浏览器的闲暇工夫执行工作,不会长时间占用主线程。
- 因为利用了闲暇工夫执行工作,所以工作须要能够被随时中断,而迭代是无奈中断的,循环是随时能够中断的,因而用循环代替迭代。
- 将比照更新操作拆分成一个个小的工作。
外围 API
requestIdleCallback 是浏览器提供的 API,其利用浏览器的闲暇工夫执行工作,如果有更高优先级的工作须要执行时,以后执行的工作可会被终止,优先执行更高优先级的工作。
requestIdleCallback 承受一个函数作为参数,该函数是要执行的工作:
requestIdleCallback(function(deadline) {// deadline.timeRemaining() 获取浏览器的空余工夫
})
API 示例
在上面的 html 实例中,页面上蕴含两个按钮,点击第一个按钮执行一段耗时操作,点击另一个按钮 alert 显示一段内容:
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>Page Title</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
</head>
<body>
<button id="work">long time work</button>
<button id="interaction">another work</button>
</body>
<script>
var workBtn = document.getElementById("work")
var interactionBtn = document.getElementById("interaction")
var iterationCount = 100000000
var value = 0
// 模仿一段耗时操作
workBtn.addEventListener("click", function () {while (iterationCount > 0) {
value =
Math.random() < 0.5 ? value + Math.random() : value + Math.random()
iterationCount = iterationCount - 1
}
})
interactionBtn.addEventListener("click", function () {alert('done another work')
})
</script>
</html>
当点击第一个按钮之后迅速点击第二个按钮,会发现页面会卡顿一段时间之后才执行 alert。
当用 requestIdleCallback API 革新之后:
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>Page Title</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
</head>
<body>
<button id="work">long time work</button>
<button id="interaction">another work</button>
</body>
<script>
var workBtn = document.getElementById("work")
var interactionBtn = document.getElementById("interaction")
var iterationCount = 100000000
var value = 0
// 模仿一段耗时操作
var expensiveCalculation = function (IdleDeadline) {
// 闲暇工夫超过 1 秒才执行
while (iterationCount > 0 && IdleDeadline.timeRemaining() > 1) {
value =
Math.random() < 0.5 ? value + Math.random() : value + Math.random()
iterationCount = iterationCount - 1
}
requestIdleCallback(expensiveCalculation)
}
workBtn.addEventListener("click", function () {requestIdleCallback(expensiveCalculation)
})
interactionBtn.addEventListener("click", function () {alert('done another work')
})
</script>
</html>
再次疾速点击第一个按钮和第二个按钮,会发现,页面迅速 alert 一段信息,阐明第一个工作并没有阻塞第二个工作。
思路
Fiber 将 Dom 比照算法分解成两步:
- 构建 Fiber 对象(可了解成一个个小的工作对象),这个过程能够随时被中断。
- 提交:将 Fiber 对象渲染成实在 Dom,这个过程是不能够被中断的。
Fiber 对象
为了可能模仿实现整个 Fiber 的外围代码,须要首先理解 Fiber 对象的构造,Fiber 对象是一个一般的 js 对象,其蕴含如下属性:
属性名 | 阐明 |
---|---|
type | 节点类型,和虚构 Dom 对象的 type 雷同,用于辨别元素、文本、组件 |
props | 节点属性,同虚构 Dom 对象 |
stateNode | 节点 Dom 对象或者组件实例 |
tag | 标记,用于标记节点 |
effects | 存储蕴含本身和所有后辈的 Fiber 数组 |
effectTag | 标记以后节点须要进行的操作,蕴含插入、更新、移除等 |
parent | 父 Fiber 对象,在 React 源码中叫 Return |
child | 以后 Fiber 对象的子级 Fiber 对象 |
sibling | 以后 Fiber 对象的下一级兄弟节点 |
alternate | Fiber 对象备份,用于比照 |
最终虚构 Dom 树会被转换成 Fiber 对象的树形构造数据,最顶层的节点 effects 属性中蕴含了该树结构所有的 Fiber 对象,其是一个数组,也就是前文说的能被中断的一个个小工作的工作操作对象。
模仿工作队列
本节将模仿实现一个工作队列,该工作队列将为后续 Fiber 执行提供根底,当浏览器有闲暇工夫时,会一直的从工作队列中取出工作并执行。
工作队列:
const createTaskQueue = () => {const taskQueue = []
return {
/**
* 向工作队列中增加工作
*/
push: item => taskQueue.push(item),
/**
* 从工作队列中获取工作
*/
pop: () => taskQueue.shift(),
/**
* 判断工作队列中是否还有工作
*/
isEmpty: () => taskQueue.length === 0}
}
export default createTaskQueue
实现闲暇工夫任务调度
render 办法是节点挂载的入口办法,当调用 render 的时候,会执行首次渲染或者比照更新,因而须要批改 render 办法。利用浏览器提供的 api 实现闲暇执行。
// 创立工作队列
const taskQueue = createTaskQueue()
// 闲暇工夫执行的具体方法
const performTask = deadline => {
// 执行工作,workLoop 办法后续补充
workLoop(deadline)
// 实现继续调用
if (subTask || !taskQueue.isEmpty()) {requestIdleCallback(performTask)
}
}
// 裸露的 render 办法
export const render = (element, dom) => {
// 1. 增加工作 =》构建 fiber 对象
taskQueue.push({
dom,
props: {children: element}
})
// 2. 指定浏览器闲暇工夫执行 performTask 办法
requestIdleCallback(performTask)
}