scheduler
TIP
本篇笔记对应的分支号为: main分支:1b55902
effect
方法的第二个入参是一个对象,该对象的其中一个属性就是 scheduler
。
什么是 scheduler
scheduler
是个函数,在首次调用 effect
时不会被执行。传入 scheduler
后,当响应式对象的属性发生变化时,effect
的第一个参数 fn
不会再次执行,而是会执行 scheduler
。如果需要执行 fn
, 需要执行 effect
返回的 runner
函数。
实现 scheduler
我们先来编写 scheduler
的测试用例:
// src/reactivity/__tests__/effect.spec.ts
it('scheduler', () => {
let dummy
let run: any
const scheduler = jest.fn(() => {
run = runner
})
const obj = reactive({ foo: 1 })
const runner = effect(() => {
dummy = obj.foo
}, { scheduler })
// 首次执行 effect 时不会调用 scheduler 方法
expect(scheduler).not.toHaveBeenCalled()
// 首次执行 effect 时会调用传入的第一个方法,此时给 dummy 赋值
expect(dummy).toBe(1)
// 触发响应式对象值改变
obj.foo++
// 会执行 scheduler 方法,但是不会执行第一个参数
expect(scheduler).toHaveBeenCalledTimes(1)
expect(dummy).toBe(1)
// 执行 runner 方法时,会执行传入的第一个方法
run()
expect(dummy).toBe(2)
})
接下来,我们根据测试用例来改造下 effect
方法。
首先,我们需要给 effect
添加第二个参数。第二个参数是个对象,可以不传,所以我们设置下默认值为空对象:
// src/reactivity/effect.ts
export function effect (fn, options: any = {}) {
const _effect = new ReactiveEffect(fn)
_effect.run()
return _effect.run.bind(_effect)
}
由于传入 scheduler
后,传入 effect
的方法 fn
在响应式对象的属性发生变化时不会被再次触发,因此我们需要在 trigger
阶段进行判断 是否传入了 scheduler
。
但是 scheduler
是在调用 effect
方法时传入的,那在 trigger
阶段如何判断是否传入了 scheduler
呢?
我们知道, trigger
阶段触发的是 ReactiveEffect
实例中的 run
方法,因此,我们可以在实例化 ReactiveEffect
阶段将 scheduler
传入,这样便可以在 trigger
时进行判断。
// src/reactivity/effect.ts
class ReactiveEffect {
private _fn: any
public scheduler: Function | undefined
constructor(fn, scheduler?: Function) {
this._fn = fn
this.scheduler = scheduler
}
run() {
activeEffect = this
return this._fn()
}
}
export function effect (fn, options: any = {}) {
const _effect = new ReactiveEffect(fn, options.scheduler)
_effect.run()
return _effect.run.bind(_effect)
}
最后,我们只需要在 trigger
时判断对应的 ReactiveEffect
实例中是否存在 scheduler
即可,如果存在 scheduler
,则执行 scheduler
方法,否则执行 run
:
// src/reactivity/effect.ts
/**
* 触发依赖
* @param target 触发依赖的对象
* @param key 触发该key对应的依赖
*/
export function trigger(target, key) {
// 根据对象与key获取到所有的依赖,并执行
const depsMap = targetMap.get(target)
const deps = depsMap.get(key)
for(const dep of deps) {
// 判断是否存在 scheduler 方法,存在的的话执行 scheduler,否则执行run
if(dep.scheduler) {
dep.scheduler()
} else {
dep.run()
}
}
}
Loading...