简单了解Virtual DOM和Fiber

3,373 阅读3分钟

标题-虚拟DOM和diff算法

React 15带来了Virtual DOMReact 16带来了Fiber。学习原理,天天向上!

Virtual DOM

直接学习此仓库github.com/Matt-Esch/v…,源码结构相当清晰!

虚拟DOM结构

Fiber

阅读了这篇文章An Introduction to React Fiber - The Algorithm Behind React

学习Fiber之前建议先学习Virtual DOMFiber是对Virtual DOM的一种升级。

  1. Virtual DOM使用栈来调度需要更新的内容,中间无法中断、暂停。Fiber支持中断,在浏览器渲染帧里面分片执行更新任务。
  2. Fiber解构让虚拟节点记录父节点、兄弟节点、子节点,形成链表树,你可以从任意顶点遍历到任意子节点,并返回。
  3. Fiber的分片操作使用requestAnimationFrame(高优先级任务)和requestIdleCallback(低优先级任务)
  4. Fiber对任务的执行优先级进行标记,高优先级的任务可以先执行,实现架构上的无阻塞

requestAnimationFrame模拟任务分片执行

MDN: window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

这里的动画可以理解为浏览器的刷新帧(对应电影里面的帧),大多数应该都是16ms

// 模拟任务中断分片执行
var tasks = new Array(100).fill(0).map((x, i) => i);
// 函数可以拿到当前执行的时刻,该值和performance.now()得到的值一致
function callFrame(timestamp) { // 片段内任务同步执行
    let t = tasks.pop();
    console.log("当前执行任务", t)

    let end = timestamp;

    // 模拟阻塞。因此这里受限于实际执行的脚本,如果运行200ms,则会阻塞下一次动画
    while(tasks.length && end - timestamp < 200) {
        end = performance.now()
    }

    //  下一帧是否需要继续运行
    if (tasks.length) {
        requestAnimationFrame(callFrame);
    }
}

requestAnimationFrame(callFrame);

requestIdleCallback模拟任务分片执行

MDN: window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。

这里的空闲,我们大多理解为一帧,但实际上间隔要大一些,实际测试最小有十几毫秒,最大有几十毫秒。

requestIdleCallback是一个实验特性,因此在利用它做任务分片的工作时,最好有一些兜底方案,比如

  • setTimeout
  • requestAnimationFrame
// 模拟任务中断分片执行
var tasks = new Array(100).fill(0).map((x, i) => i);
// deadline可以获取当前剩余的空闲时间
function call(deadline) {
    let start = performance.now();
    let t = tasks.pop();
    console.log("当前执行任务", t)

    let end = start;
    // 模拟阻塞
    while(tasks.length && end - start < 20) {
        end = performance.now()
         // deadline.timeRemaining()可以获取当前剩余时间
         // 我们实际运行的时候,可以用这个deadline.timeRemaining()来判断是否要执行下一个任务
        console.log("deadline remaining", deadline && deadline.timeRemaining())
    }
    //  下一帧是否需要继续运行
    if (tasks.length) {
        requestIdleCallback(call);
    }
}

diff算法

这个就是树或者图的遍历,建议多刷leetcode就行了。

patch算法

对节点进行具体的增删改

  • 增加的节点,加进vdom
  • 节点属性变更,修改对应的vnode
  • 兄弟变更,修改对应的节点数组
  • 父节点变更,触发子节点的patch