Vue3实现批量异步更新
作者:前端胖头鱼
写在前面
这是Vue3源码分析的第三篇,与响应式系统中调度执行有关,其中computed、watch等核心功能都离不开它,可见其重要程度。
除了实现可调度性,我们还会借助它来实现vue中一个非常重要的功能,批量更新或者叫异步更新
多次修改数据(例如自身num10次),只进行一次页面渲染(页面只会渲染最后一次num10)。
什么是调度执行
什么是调度执行?
指的是响应式数据发生变化出发副作用函数重新执行时,我们有能力去决定副作用函数的执行时机、次数和方式。
来看个例子
const state = reactive({ num: 1 }) effect(() => { console.log('num', state.num) }) state.num++ console.log('end')
如果我们想要它按照这个顺序书序呢?
1
end
2
你可能会说,我调换一下代码顺序就好了哇!!!
const state = reactive({ num: 1 }) effect(() => { console.log('num', state.num) }) console.log('end') state.num++
瞬间就解决了问题。不过看起来这不是我们想要最终答案。
我们想要通过实现可调度性来解决这个问题。
如何实现可调度
我们从结果出发来思考如何实现可调度的特性。
const state = reactive({ num: 1 }) effect(() => { console.log(state.num) }, { // 注意这里,假如num发生变化的时候执行的是scheduler函数 // 那么end将会被先执行,因为我们用setTimeout包裹了一层fn scheduler (fn) { // 异步执行 setTimeout(() => { fn() }, 0) } }) state.num++ console.log('end')
看到这里也许你已经明白了,我们将通过scheduler来自主控制副作用函数的执行时机。
在这之前,执行state.num++
之后,console.log(state.num)
将会被马上执行,而添加scheduler后,num发生变化后将执行scheduler中的逻辑。
源码实现
虽然可调度性在Vue中非常重要,但实现这个机制却非常简单,我们甚至只要增加两行代码就可以搞定。
第一行代码
// 增加options参数 const effect = function (fn, options = {}) { const effectFn = () => { // .... } // ... // 将options参数挂在effectFn上,便于effectFn执行时可以读取到scheduler effectFn.options = options }
第二行代码
function trigger(target, key) { // ... effectsToRun.forEach((effectFn) => { // 当指定了scheduler时,将执行scheduler而不是注册的副作用函数effectFn if (effectFn.options.scheduler) { effectFn.options.scheduler(effectFn) } else { effectFn() } }) }
是不是简单到离谱?
批量更新 & 异步更新
来看段诡异的代码,请问num会被执行多少次?100还是101?
const state = reactive({ num: 1 }) effect(() => { console.log('num', state.num) }) let count = 100 while (count--) { state.num++ }
对于页面渲染来说1到101中间的2~100仅仅只是过程,并不是最终的结果,处于性能考虑Vue只会渲染最后一次的101。
Vue是如何做到的呢?
利用可调度性,再加点事件循环的知识,我们就可以做到这件事。
- num的每次变化都会导致scheduler的执行,并将注册好的副作用函数存入jobQueue队列,因为Set本身的去重性质,最终只会存在一个fn
- 利用Promise微任务的特性,当num被更改100次之后同步代码全部执行结束后,then回调将会被执行,此时num已经是101,而jobQueue中也只有一个fn,所以最终只会打印一次101
const state = reactive({ num: 1 }) const jobQueue = new Set() const p = Promise.resolve() let isFlushing = false const flushJob = () => { if (isFlushing) { return } isFlushing = true // 微任务 p.then(() => { jobQueue.forEach((job) => job()) }).finally(() => { // 结束后充值设置为false isFlushing = false }) } effect(() => { console.log('num', state.num) }, { scheduler (fn) { // 每次数据发生变化都往队列中添加副作用函数 jobQueue.add(fn) // 并尝试刷新job,但是一个微任务只会在事件循环中执行一次,所以哪怕num变化了100次,最后也只会执行一次副作用函数 flushJob() } }) let count = 100 while (count--) { state.num++ }
到此这篇关于Vue3实现批量异步更新的文章就介绍到这了,更多相关Vue3异步更新内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!