优化stop
测试用例
还是之前的测试,区别在于将obj.prop = 3修改为obj.prop++,这时单元测试不能通过。
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 = 3
// 通过 obj.prop++ 触发set
obj.prop++
expect(dummy).toBe(2)
runner()
expect(dummy).toBe(3)
})
问题原因
obj.prop++ 相当于 obj.prop = obj.prop + 1
obj.prop + 1,同时做了读和写的操作
调用onStop的时候会调用cleanUpEffect清除依赖,所以obj.prop = 3时,dummy还是等于2
但是如果使用obj.prop++,会先进行读的操作,会调用get方法,因此会重新收集依赖,所以obj.prop++执行完成后,传入effect的fn又作为依赖被收集,所以dummy会继续更新。
综上所述:要解决这个问题,需要在调用stop之后,进行读的操作时不让它收集依赖就可以了;当再次调用runner,又开始收集依赖。
解决
// 声明一个全局变量 shouldTrack 标识是否应该收集依赖
let shouldTrack // 相当于收集依赖的开关,默认是关的
// 更新track函数
function track (target, key) {
// 收集依赖,这些依赖是唯一的(set数据结构)
// target ---对应---> key ---对应---> dep
let depsMap = targetMap.get(target) // 一个对象的key对应的所有依赖保存在这里面
// 初始化
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
if (!activeEffect) return
// 如果 shouldTrack == false(不需要收集依赖), 直接return掉
if (!shouldTrack) return
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
// 更新ReactiveEffect
class ReactiveEffect {
... // 省略其他代码
run () {
/*
* 根据active判断它是不是stop状态
* stop状态:依赖已经清除了,并且不需要去收集依赖
* 直接调用传入的fn就可以
* */
if (!this.active) {
return this._fn()
}
/*
* 不是stop状态:需要收集依赖
* */
shouldTrack = true // 不是stop状态应该将开关打开(不是stop状态,就让它可以收集依赖)
activeEffect = this // 当前的effect实例
const result = this._fn() // 执行fn,会触发响应式对象的get操作,在get操作中去收集依赖
// 需要reset的操作
shouldTrack = false // 重置为false,默认是不能收集依赖的
return result
}
...
}
优化
// track函数中
function track (target, key) {
// 优化点1:如果不需要收集依赖,下面的代码不需要执行了
// 将两个判断上移并抽出来
if (!isTracking()) return
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
// 优化点2:如果依赖已经收集了,则直接return掉
if (dep.has(activeEffect)) return
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
function isTracking () {
return shouldTrack && activeEffect !== undefined
}