实现effect的stop功能
通过stop函数传入effect返回的runner 再次修改响应式对象的值的时候 不会修改成功
其实主要思路就是在调用stop函数的时候将 收集的effect依赖移除掉
老样子先给上测试用例:
it('stop',()=>{
// 通过stop函数传入effect返回的runner 再次修改响应式对象的值的时候 不会修改成功
// 其实主要思路就是在调用stop函数的时候将 收集的effect依赖移除掉
let dummy
const obj = reactive({prop:1})
const runner = effect(()=>{
dummy = obj.prop
})
obj.prop = 2
expect(dummy).toBe(2)
stop(runner)
obj.prop = 3
expect(dummy).toBe(2)
runner()
expect(dummy).toBe(3)
})
大体stop功能实现的步骤:
1 需要在effect中导出stop函数,导出的stop函数中通过传入的runner去调用effect实例上的stop方法
2 所以需要给传入的runner挂载上effect函数
3 需要在ReactiveEffect类上提供stop方法,stop方法中要获取到对应effect的dep依赖,这就需要我们在track依赖收集的时候去做一下反向收集dep
4 最后循环deps数组中的每一个dep,进行一个effect依赖的移除
实现的部分截图说明:
以下是代码实现:
effect和stop函数的代码:
export function effect(fn,options:any = {}){
const _effect = new ReactiveEffect(fn,options.scheduler)
_effect.run()
const runner:any = _effect.run.bind(_effect)
runner.effect = _effect
return runner
}
export function stop(runner) {
runner.effect.stop()
}
track函数:
export function track(target,key){
// target -> key -> dep
let depsMap = targetMap.get(target)
if(!depsMap){
depsMap = new Map()
// 没有depsMap时要加进去
targetMap.set(target,depsMap)
}
let dep = depsMap.get(key)
if(!dep){
dep = new Set()
depsMap.set(key,dep)
}
dep.add(activeEffect)
// 这里需要反向收集一下dep
activeEffect.deps.push(dep)
}
ReactiveEffect类中的stop函数:
stop(){
this.deps.forEach((dep:any) => {
dep.delete(this)
})
}
重构
根据tdd的开发流程来说,第一步是写测试,第二步是让测试通过,第三步是对代码的一个重构,下面我们就对我们的代码进行重构,让它更有可读性
这里主要有两个优化点:
1 将移除依赖的函数抽离出来封装,更加简洁
2 使用active变量,防止使用者多次调用stop时重复触发
因为onStop功能和stop功能相关,所以就一起在这里介绍了
onstop主要就是在effect函数的第二个配置项中传入,然后当调用stop函数的时候,onStop将会被执行
接下来是onStop的测试代码:
it('onStop',()=>{
// onstop主要就是在effect函数的第二个配置项中传入,然后当调用stop函数的时候,onStop将会被执行
const obj = reactive({
foo:1
})
const onStop = jest.fn()
let dummy
const runner = effect(
()=>{
dummy = obj.foo
},
{
onStop
}
)
stop(runner)
expect(onStop).toBeCalledTimes(1)
})
这个实现起来就比较简单了,实现onStop的思路:
1 effect中接受onStop
2 在effect中调用stop函数的时候如果传入了onStop就执行onStop
改动只有红色部分,代码不多就不上代码了
接下来就是老规矩了,重构,这里我们有什么可以重构的呢?其实最主要可以重构的点就是effect的第二个配置项参数,因为每次多传入一个配置项时就需要我们手动的去接收一下,其实是很麻烦的。因此我们就可以使用Object.assign将传入的options加入到effect中去
如图红框:
但是一想,其实这种通用性极强的公共函数我们是可以抽离出去给所有其他模块使用:
所以我们就把他写到了shared/index.ts中的公共函数模块中去
然后直接在effect中导入使用了:
yarn test时报错问题:
最后,因为我们一直测试的是effect的测试文件(yarn test effect),这个文件的测试用例是通过的了,这时候我们这个模块的功能写完了之后,一般需要yarn test测试一下你所有功能,会不会有问题。当我们yarn test测试所有的时候就出问题了,报错如下
知道问题在哪了,那其实问题就解决了百分之90了,我们找到收集依赖的track函数,然后对activeEffect进行判断,当没有activeEffect的时候就直接return掉track,不再往下走,如图红框: