手写虚拟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());
});
};
如上代码,一看便知:
- 不管来多少个(即将更新的)组件,先放入队列;
- 然后调用系统方法插入到系统的微/宏任务队列中,等待被执行;
- 执行时,通过Set将相同的组件合并为一个,然后依次执行;
三、总结
本文基于上一个版本的代码,加入了异步更新的支持,提高渲染性能。
项目源码:
https://github.com/qingye/VirtualDOM-Study/tree/master/VirtualDOM-Study-07