分析 Vue3 Effect 函数看响应式依赖收集和触发更新过程2

分析如下示例更新过程:

let obj = { name: "Long", age: 10, flag: true }

const state = reactive(obj);

effect(() => {
  app.innerHTML = state.flag ? state.name : state.age
})

setTimeout(() => {
  state.flag = false
}, 1000)
  • 执行 effect 进行了依赖收集,收集的形式如下:

    tergetMap
    
    target     depsMap = new Map()
    						flag:        dep = new Map()
    						
    						name:        dep = new Map()
    
  • 由于首次执行 effect 传入的回调函数,访问 state.flagstate.name 会进行依赖收集。会在 effect._trackId 为 1 的 effect 的 deps 数组上保存 flag 和 name 这两个 dep,同时也分别在 flag 的 dep 和 name 的 dep 上保存 effect._trackId 为 1 的 effect 。双向记忆。

    tergetMap
    
    target     depsMap = new Map()
    						flag:        dep = new Map()
    																effect(_trackId:1)
    						
    						name:        dep = new Map()
    																effect(_trackId:1)
    
    effect
    deps: [
    	flag: dep = new Map(),
    	name: dep = new Map()
    ]
    
  • state.flag = false 时触发 trigger,执行 flag 的 dep 中的 effect.run()

  • 执行 run 方法,会将 activeEffect = this 并调用 preCleanEffect 将 effect._depsLength = 0 置为 0, effect._trackId++。此时的 activeEffect 已经变样了(即使是从之前的 flag 的 dep 中取出的 effect)

  • 执行 run 方法中的 this.fn() 就是执行 effect 的回调函数。访问 state.flag 和 state.age 引发依赖收集。

function preCleanEffect(effect) {
  effect._depsLength = 0
  effect._trackId++
}

run() {
    this._dirtyLevel = DirtyLevels.NoDirty // 每次运行后effect变为no_dirty
    // 让fn执行
    if (!this.active) {
      return this.fn() // 不是激活的,执行后,什么都不用做
    }
    let lastEffect = activeEffect
    try {
      activeEffect = this

      // effect重新执行前,需要将上一次的依赖情况  effect.deps

      preCleanEffect(this)
      this._running++
      return this.fn() // 依赖收集  -> state.name  state.age
    } finally {
      this._running--
      postCleanEffect(this)
      activeEffect = lastEffect
    }
  }
  stop() {
    if (this.active) {
      this.active = false // 后续来实现
      preCleanEffect(this)
      postCleanEffect(this)
    }
  }
  • 访问 state.flag 时,触发 getter,调用 track,进而调用 trackEffect(activeEffect, dep)。这个 activeEffect 可以理解为新的 effect(length=0,trackId = 2),dep 是从flag 的 depsMap 中获取的
  • 调用 trackEffect 函数执行 dep.get(effect) !== effect._trackId 时,dep.get(effect) 获取到的是 depsMap 中的 value 为 1。effect._trackId 已经在上次执行 run 方法的 fn 时调用 preCleanEffect 设置为 2 了。所以 dep.get(effect) !== effect._trackId 为 true,调用 dep.set(effect, effect._trackId) 更新 id。此时 flag dep 上的 effect 的 trackId 也变成 2 了。当获取 let oldDep = effect.deps[effect._depsLength] 时,effect.deps[0] 即 flag 的 dep,和 effect(之前也是从 flag 上获取的)进行比对。当然相等!就开始下一个effect._depsLength++
  • 访问 state.age 同样触发 getter,调用 track,进而调用 trackEffect(activeEffect, dep)。由于之前根本没有访问过 state.age 导致 age 的 dep 上没有 effect,即 dep.get(effect) 为 undefined, 而 effect._trackId 已经为 2,不相等。则设置 dep.set(effect, effect._trackId) , age 的 dep value 为 2。当获取 oldDep 拿到的还是 name 的 dep ,明显和现在的 age 的 dep 不等,就清除掉 name 的 dep使用 age 的 dep 更新 effect.deps[effect._depsLength++] = dep
export function trackEffect(effect, dep) {
  // 收集时一个个收集的
  // 需要重新的去收集依赖 , 将不需要的移除掉
  // console.log(effect, dep);

  if (dep.get(effect) !== effect._trackId) {
    dep.set(effect, effect._trackId) // 更新id
    // {flag,name}
    // {flag,age
    let oldDep = effect.deps[effect._depsLength]
    // 如果没有存过
    if (oldDep !== dep) {
      if (oldDep) {
        // 删除掉老的
        cleanDepEffect(oldDep, effect)
      }
      // 换成新的
      effect.deps[effect._depsLength++] = dep // 永远按照本次最新的来存放
    } else {
      effect._depsLength++
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值