文章目录
- 一、什么是调度执行
- 二、如何实现可调度?
- 三、批量更新 `&` 异步更新
- 四、`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包裹了一层fnscheduler (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执行时可以读取到schedulereffectFn.options = options
}
function trigger(target, key) {
// ...effectsToRun.forEach((effectFn) => {// 当指定了scheduler时,将执行scheduler而不是注册的副作用函数effectFnif (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 = 100while (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 = falseconst flushJob = () => {if (isFlushing) {return}isFlushing = true// 微任务p.then(() => {jobQueue.forEach((job) => job())}).finally(() => {// 结束后充值设置为falseisFlushing = false})
}effect(() => {console.log('num', state.num)
}, {scheduler (fn) {// 每次数据发生变化都往队列中添加副作用函数jobQueue.add(fn)// 并尝试刷新job,但是一个微任务只会在事件循环中执行一次,所以哪怕num变化了100次,最后也只会执行一次副作用函数flushJob()}
})let count = 100while (count--) {state.num++
}
五、最后
本人每篇文章都是一字一句码出来,希望对大家有所帮助,多提提意见。顺手来个三连击,点赞👍收藏💖关注✨,一起加油☕