React 16 以前的版本比对更新 VirtualDOM 的过程是采用循环加递归实现的,这种比对方式有一个问题,就是一旦任务开始进行就没法中断,若是应用中组件数量庞大,主线程被长期占用,直到整棵 VirtualDOM 树比对更新完成以后主线程才能被释放,主线程才能执行其余任务。这就会致使一些用户交互,动画等任务没法当即获得执行,页面就会产生卡顿, 很是的影响用户体验。html
Fiber就是React提出的用于解决页面卡顿的方案,包含以下三个方面:算法
requestIdleCallback 是浏览器提供的API,其利用浏览器的空闲时间执行任务,若是有更高优先级的任务须要执行时,当前执行的任务可会被终止,优先执行更高优先级的任务。数组
requestIdleCallback接受一个函数做为参数,该函数是要执行的任务:浏览器
requestIdleCallback(function(deadline) { // deadline.timeRemaining() 获取浏览器的空余时间 })
在下面的html实例中,页面上包含两个按钮,点击第一个按钮执行一段耗时操做,点击另外一个按钮alert显示一段内容:dom
<!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一段信息,说明第一个任务并无阻塞第二个任务。ui
Fiber将Dom对比算法分解成两步:线程
为了可以模拟实现整个Fiber的核心代码,须要首先了解Fiber对象的结构,Fiber对象是一个普通的js对象,其包含以下属性:code
属性名 | 说明 |
---|---|
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对象,其是一个数组,也就是前文说的能被中断的一个个小任务的任务操做对象。