React fiber原理解析及自定义实现(一)

概述

React 16 以前的版本比对更新 VirtualDOM 的过程是采用循环加递归实现的,这种比对方式有一个问题,就是一旦任务开始进行就没法中断,若是应用中组件数量庞大,主线程被长期占用,直到整棵 VirtualDOM 树比对更新完成以后主线程才能被释放,主线程才能执行其余任务。这就会致使一些用户交互,动画等任务没法当即获得执行,页面就会产生卡顿, 很是的影响用户体验。html

Fiber就是React提出的用于解决页面卡顿的方案,包含以下三个方面:算法

  1. 利用浏览器的空闲时间执行任务,不会长时间占用主线程。
  2. 由于利用了空闲时间执行任务,因此任务须要能够被随时中断,而迭代是没法中断的,循环是随时能够中断的,所以用循环替代迭代。
  3. 将对比更新操做拆分红一个个小的任务。

核心API

requestIdleCallback 是浏览器提供的API,其利用浏览器的空闲时间执行任务,若是有更高优先级的任务须要执行时,当前执行的任务可会被终止,优先执行更高优先级的任务。数组

requestIdleCallback接受一个函数做为参数,该函数是要执行的任务:浏览器

requestIdleCallback(function(deadline) {
  // deadline.timeRemaining() 获取浏览器的空余时间
})

API示例

在下面的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对比算法分解成两步:线程

  1. 构建Fiber对象(可理解成一个个小的任务对象),这个过程能够随时被中断。
  2. 提交:将Fiber对象渲染成真实Dom,这个过程是不能够被中断的。

Fiber对象

为了可以模拟实现整个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对象,其是一个数组,也就是前文说的能被中断的一个个小任务的任务操做对象。

相关文章
相关标签/搜索