手写虚拟DOM(七)—— 异步更新

2020-12-28  本文已影响0人  青叶小小

本文为系列文章:

手写虚拟DOM(一)—— VirtualDOM介绍
手写虚拟DOM(二)—— VirtualDOM Diff
手写虚拟DOM(三)—— Diff算法优化
手写虚拟DOM(四)—— 进一步提升Diff效率之关键字Key
手写虚拟DOM(五)—— 自定义组件
手写虚拟DOM(六)—— 事件处理
手写虚拟DOM(七)—— 异步更新

一、前言

我们前面的代码,每次setState都立即更新,也就是我们说的同步更新,
但是如果在一个方法里多次setState,则将会多次同步更新,这会降低我们的渲染性能。
实际上,在React中,更新是异步的,如果在一个方法里多次setState,最终也只会更新一次。

二、实现异步更新

修改我们的DemoComp(为了之后方面模拟异步更新):

class DemoComp extends Component {
    ......
    interval = () => {
        setInterval(() => {
            this.setState({name: 'chris-', value: this.state.value + 1});
            this.setState({name: 'chris-', value: this.state.value + 1});
        }, 2000);
    };
    ......
}

连续调用setState。

setState方面在我的基类组件中:

class Component {
    ......
    setState(newState) {
        this.state = {...this.state, ...newState};
        diff(this._dom, this.render());
    }
    ......
}

// 改为
class Component {
    ......
    setState(newState) {
        this.state = {...this.state, ...newState};
        enqueue(this);
    }
    ......
}

同步调用 diff 方法,执行同步更新,因此,我们需要修改这里,使得能够支持异步更新。

小插曲:什么是宏任务与微任务?
---------------------------------------------------------------------------------
[JS同步异步]
- 同步任务:指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
- 异步任务:指的是不进入主线程,某个异步任务可以执行了,该任务才会进入主线程执行

异步执行的运行机制如下[同步任务也如此,因为它可以被视为没有异步任务的异步执行]:
 1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack);
 2. 主线程之外,还存在一个“任务队列”,只要异步任务有了运行结果,就在“任务队列”之中放置一个事件;
3. 一旦“执行栈”中的所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件
   那些对应的异步任务,于是结束等待,进入执行栈,开始执行;
4. 主线程不断重复第3步;

---------------------------------------------------------------------------------
我们知道JS是单线程的,因此执行代码也是一句一句的解释再执行;
如果我们想用异步操作,无非就是使用类似方式:
1. setTimeout/setInterval (ES2015)
2. Promise (ES6)
3. script
4. process.nextTick
诸如等等.....

不同类型的任务会进入不同的Event Queue,有宏任务的队列和微任务的队列,以上任务分类如下:
- macro-task(宏任务):包括整体代码script,setTimeout,setInterval
- micro-task(微任务):Promise,process.nextTick

事件循环的顺序,决定js代码的执行顺序:
1. 进入整体代码(宏任务)后,开始第一次循环;
2. 接着执行所有的微任务;
3. 再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务;

从上面可以看出来两点:
1. 微任务是在一次循环结束后开始执行;
2. 宏任务是在一次循环开始时执行;

const pendingRenderQueue = [];

const enqueue = (component) => {
    if (pendingRenderQueue.push(component) === 1) {
        if (typeof Promise === 'function') {
            Promise.resolve().then(doRender); // 微任务
        } else {
            setTimeout(doRender, 0);          // 宏任务
        }
    }
};

const doRender = () => {
    // 将同一个组件的多次 setState 操作合并为一次,然后再更新
    const uniques = [...new Set(pendingRenderQueue)];

    // 清空队列
    pendingRenderQueue.splice(0, pendingRenderQueue.length);

    // 依次更新各个组件
    uniques.forEach(comp => {
        diff(comp._dom, comp.render());
    });
};

如上代码,一看便知:

三、总结

本文基于上一个版本的代码,加入了异步更新的支持,提高渲染性能。

项目源码:
https://github.com/qingye/VirtualDOM-Study/tree/master/VirtualDOM-Study-07

上一篇 下一篇

猜你喜欢

热点阅读