vue3 - mini-vue(五)优化stop

162 阅读2分钟

优化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
}