本文已参与「新人创作礼」活动,一起开启掘金创作之路。
上篇我们实现了 vue设计与实现-避免无限递归循环,本篇我们聊一聊调度器。
为什么需要调度器
在前几篇文章中,我们都是通过执行effect函数,在effect函数中读取代理的数据,收集依赖。然后修改数据,触发依赖。
我们在实际开发的业务中,可能不想在修改数据后,立马触发依赖。
1.比如我修改代理数据newObj.cout的值,我修改了10次,我只想在最后调用一次就可以了。
2.比如我们在想控制触发依赖的时机,比如获取到某个数据之后,再去触发依赖
调度器的功能就是为了将effect的使用权交给用户,从而控制依赖触发的次数和时机
例子
const obj={
count:1
}
const newObj=new Proxy(obj,{
get(){
...
},
set(){
...
}
})
effect(()=>{
console.log(newObj.count);
})
newObj.count=3
console.log(newObj.count)
console,log("输出新值")
如上代码所示,上面的代码会依次输出
1 3 ”输出新值“
但是我们理想的输出是 1 “输出新值” “3”,我们要如何才能做到呢
思考
我们想要在合适的时机去触发effect,肯定要从trigger入手,目前我们是获取到effectFn()之后立马触发的,我们可以在这里做一些“文章”
function trigger(target,key){
let depMaps=bucket.get(target)
let deps=depMaps.get(key)
let effectTorun=new Set(deps)
effectTorun.forEach(effectFn => {
//增加防止无限递归循环的代码
if(effectFn!==activeEffect){
//我们可以在这里做一些“文章”
effectFn()
}
});
}
大胆设想一下:比如我们有一个函数schedule,这个函数可以将effectFn 传进去,然后去控制effectFn的执行时机和执行次数。
可是这个函数要在哪里定义呢? 要怎么拿到呢?
方案
先说方案;
这个schedule 可以通作为effect函数的参数传入 然后作为effectFn的一个属性,这样,在trigger的时候就可以拿到用户传入的schedule了
一.effect代码如下
let effect=(fn,option={})=>{
//清空effectFn 的deps
const effectFn=()=>{
cleanUp(effectFn)
activeEffect=effectFn
effectStack.push(effectFn)
const res=fn()
effectStack.pop()
activeEffect=effectStack[effectStack.length-1]
return res
}
effectFn.deps=[]
effectFn.option=option
if(option.lazy){
return effectFn
}else{
effectFn()
}
}
注意看 option 相关代码
1.这里将option 初始化为一个空对象
2.将option 挂载在effectFn的option上面
二.再来看trigger的代码
function trigger(target,key){
let depMaps=bucket.get(target)
let deps=depMaps.get(key)
let effectTorun=new Set(deps)
effectTorun.forEach(effectFn => {
//增加防止无限递归循环的代码
if(effectFn!==activeEffect){
if(effectFn.option.schedule){
effectFn.option.schedule(effectFn)
}else{
effectFn()
}
}
});
}
这里判断option对象里面有没有schedule,如果有,则将effect使用权交给schedule,如果没有,则会自动执行
如何使用呢?
三.使用的代码如下
effect(()=>{
console.log(newObj.count)
},{
schedule:(fn)=>{
setTimeout(() => {
fn()
}, 500);
}
})
newObj.count=3
console.log("输出新值")
这样,我们输出的顺序就是
1 “输出新值” 3
当然,我们熟知的computed 和watch 都会使用到schedule