Vue的异步更新机制
我们都知道Vue的更新是异步,那我们就知道在js中,异步任务就依赖于事件循环的机制。事件循环的机制就是一个执行模型,用于处理异步任务的执行顺序。那Vue就是借助了这一点,就实现了异步更新。
举个简单的例子
<script setup lang="ts">
import { ref } from "vue";
const state = ref({ count: 0 });
const fn = () => {
state.value.count = 2;
state.value.count = 3;
state.value.count = 4;
state.value.count = 5;
state.value.count = 6;
console.log(state.value.count);
};
</script>
<template>
<div @click="fn">{{ state.count }}</div>
</template>
当我们在执行上面方法的时候,多次修改了数据,如果我们每一次修改数据,就要进行页面渲染,那我们的效率就很低,我们会把多次修改的数据监听到的方法放在一个队列中,最后统一处理后,只进行一次页面的渲染,页面指挥渲染最后一次 state.count 6
Vue的异步实现过程
我们通过上面知道了Vue的异步更新原理,那我们该如何实现以上功能呢?
1.我们想到既然是要异步执行,那么我们的Promise肯定是要用上的
2.我们在多次改变值的过程中,多次调用trigger执行effect时,我们就得把这些变更收集起来
所以我们很容易就写出来以下代码:
queueJob
- 该方法负责维护一个主任务队列,接受函数作为参数,会将参数push到queue队列中。在当前宏任务执行完成后,清空队列
let queue = [];
export function queueJob(job) {
if(!queue.includes(job)){
queue.push(job);
queueFlush();
}
}
queueFlush
- 该方法负责尝试创建微任务,等待任务队列执行
let isFlushPending = false;
function queueFlush(){
if(!isFlushPending){
isFlushPending = true;
Promise.resolve().then(flushJobs)
}
}
flushJobs
- 根据Id排队队列,这就是先刷新父在刷新子的原因
- 遍历执行队列的任务
- 执行完毕后在清空队列
function flushJobs(){
isFlushPending = false
// 清空时 我们需要根据调用的顺序依次刷新
// 保证先刷新父在刷新子
queue.sort((a,b) => a.id - b.id);
for(let i = 0; i < queue.length; i++){
const job = queue[i];
job();
}
queue.length = 0;
}
总结
Vue 的异步更新机制带来了以下几个优势:
- 性能优化:将多个数据变更合并成一次更新,避免频繁的 DOM 操作,提高渲染效率。
- 批量更新:将数据变更放入一个队列中,只在下一个事件循环中进行更新,避免同步更新可能引发的性能问题和 UI 闪烁现象