上一篇Vue3.0源码系列(二):响应式原理(实现effect内部的runner和scheduler)
这一篇为大家介绍源码中stop和onStop两个方法的实现,还是老规矩,如果我们想实现功能,首先书写单元测试,基于单元测试,开发功能逻辑,保证各项单元测试case全部通过,最后对书写的代码进行重构(TDD思想),不喜勿喷哈。
stop功能的实现:
1.首先,书写stop的单元测试,运行stop方法,参数时我们的effect的返回值,当我们响应式数据发生变化时候,我们发现dummy值并没有改变,也就是说源码中stop方法阻止了effect的依赖触发。当我们手动执行runner时候,effect依赖重新执行,数据发生了变化。
it('stop',()=>{
let dummy;
const obj = reactive({prop:1})
const runner= effect(()=>{
dummy = obj.prop
})
obj.prop = 2
expect(dummy).toBe(2)
stop(runner)
obj.prop++
expect(dummy).toBe(2)
runner()
expect(dummy).toBe(3)
})
2.那么我们来一点一点实现源码中的stop功能,看看他到底是一个什么东东,如此神秘那
export function stop(runner) {
// runner就是一个effect实例,指向ReactiveEffect中的的stop方法
runner.effect.stop()
}
export class ReactiveEffect{
private _fn:any //内部的_fn()方法
deps = []
active = true // avtive 属性控制stop方式执行的状态,stop执行其变为false
//public scheduler 外部能够获取到scheduler方法
constructor(fn) {
//接受fn方法
this._fn = fn
}
//每次effect被调用时候,执行run方法,进而执行fn()方法
stop() {
//当this.active为true时,代表stop第一次执行。
if(this.active) {
//清除所有的effect
cleanupEffect(this)
//执行之后变为false,防止重复调用,优化性能。
this.active = false
}
}
}
function cleanupEffect(effect) {
//取出effect中反向收集到的deps,循环删除dep中的effect
effect.deps.forEach((dep:any)=>{
dep.delete(effect)
})
effect.deps.length = 0
}
export function effect(fn,options:any = {}) {
// 创建 ReactiveEffect 类,生成effect工厂,每次生成唯一的effect,每次调用effect的时候,执行fn方法。
const _effect = new ReactiveEffect(fn,options.scheduler)
//利用extend方法实现effect的第二个参数options的合并。
extend(_effect,options)
_effect.run()
//处理_effect指针,当前的实例的run方法
const runner:any = _effect.run.bind(_effect)
//将当前_effect挂载到runner的effect上,方便stop方法获取。
runner.effect = _effect
//返回一个runner函数
return runner
}
通过源码中我们可以看到,stop方法的底层实现其实就是,把runner执行所收集到的所有effect从deps中删除,然后关上active开关,当响应式数据再发生变化的时候,deps中的effect都被删除,所以不会触发set。为了进行代码性能优化,当重复调用stop时候,因为有active开关,也只会调用一次stop。
onStop功能实现
1:onStop方法其实就是stop方法的回调,通过effect的第二个参数传进去类似effect(fn1,fn2),当我们执行完stop函数后,onStop会在之后进行执行,下面是onStop方法的单元测试。
it('onStop',()=>{
const obj = reactive({
foo:1
})
const onStop = jest.fn()
let dummy;
const runner = effect(
()=>{
dummy = obj.foo
},
{
onStop
}
)
stop(runner);
//单元测试(1)
expect(onStop).toBeCalledTimes(1);
})
2.那么我们就来看看onStop的源码怎么实现那,在stop方法执行完后,通过判断方法this.onStop,来执行回调this.onStop()方法,这是我们看到单元测试(1)被执行了1次,证明onStop方法被调用了。
export function effect(fn,options:any = {}) {
// 创建 ReactiveEffect 类,生成effect工厂,每次生成唯一的effect,每次调用effect的时候,执行fn方法。
const _effect = new ReactiveEffect(fn,options.scheduler)
//利用extend方法实现effect的第二个参数options的合并,extend方法就是封装的 object.assign,把传过来的onStop参数进行合并。
extend(_effect,options)
_effect.run()
//处理_effect指针,当前的实例的run方法
const runner:any = _effect.run.bind(_effect)
//将当前_effect挂载到runner的effect上,方便stop方法获取。
runner.effect = _effect
//返回一个runner函数
return runner
}
export class ReactiveEffect{
private _fn:any //内部的_fn()方法
deps = []
active = true // avtive 属性控制stop方式执行的状态,stop执行其变为false
onStop?:()=>void //可选方法用?: 声明
constructor(fn) {
//接受fn方法
this._fn = fn
}
stop() {
//当this.active为true时,代表stop第一次执行。
if(this.active) {
//清除所有的effect
cleanupEffect(this)
//当stop方法之后执行之后,onStop存在则会被执行,onStop为用户传入的第二个参数,执行用户传入的回调,也就是stop方法的回调
if(this.onStop) {
this.onStop()
}
//执行之后变为false,防止重复调用,优化性能。
this.active = false
}
}
}
通过上面stop和onStop的源码分析,相信你会对他们有一个更加深入的了解,以后我们在vue3应用中,会更加得心应手,持续更新中......