Vue源码学习(六)——ReactiveEffect(依赖处理优化)

之前我们了解过ReactiveEffect的核心就是run方法,现在我们主要了解他整体的构建思想

在track函数中     1.dep.add(activeEffect!)
                           2.activeEffect!.deps.push(dep)   

不仅activeEffect记录到dep中,并且将dep放入activeEffect中的deps进行反向记录,那么为什么进行这一步操作呢----(主要目的就是为了让每一次更新后的依赖不会出错)

比如 watchEffect( console.log(msg.flag?'hello':msg.name))  当我们刚开始进行依赖收集的时候,这个effect时加入msg.name的dep中,但是当msg.flag为true的时候,表达式返回hello,和msg.name无关了,当msg.name触发trigger的时候,还会触发当前这个effect嘛????-------- 不会,因为Vue会根据deps进行处理,每当触发当前effect的时候,Vue会清空deps,然后也会根据deps中的dep删除effect (详细可以看cleanupEffect源码)

function cleanupEffect(effect: ReactiveEffect) {
  // 找到所有依赖这个 effect 的响应式对象
  // 从这些响应式对象里面把 effect 给删除掉
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}

export class ReactiveEffect<T = any> {
  active = true
  //dep 数组,在响应式对象收集依赖时也会将对应的依赖项添加到这个数组中
  deps: Dep[] = []
  parent: ReactiveEffect | undefined = undefined

  /**
   * Can be attached after creation
   * @internal
   */
  computed?: ComputedRefImpl<T>
  /**
   * @internal
   */
  allowRecurse?: boolean
  /**
   * @internal
   */
  private deferStop?: boolean

  onStop?: () => void
  // dev only
  onTrack?: (event: DebuggerEvent) => void
  // dev only
  onTrigger?: (event: DebuggerEvent) => void

  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null,
    scope?: EffectScope
  ) {
    recordEffectScope(this, scope)
  }

  run() {
    // 当active=false,执行函数但不收集依赖
    if (!this.active) {
      return this.fn()
    }
    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack

    //循环遍历parent的父级元素
    //直到找到了与当前实例this相同的父级元素,或者直到parent为null为止
    //避免在组件的父子关系中出现循环引用的情况
    while (parent) {
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    try {
      //将上一个activeEffect先保存起来
      this.parent = activeEffect
      // 将自己设置为当前活跃的 ReactiveEffect
      activeEffect = this
      shouldTrack = true
      //标记effect层级,执行一层effect,++effectTrackDepth
      trackOpBit = 1 << ++effectTrackDepth
      // 超过 maxMarkerBits 则 trackOpBit 的计算会超过最大整形的位数,降级为 cleanupEffect 
      if (effectTrackDepth <= maxMarkerBits) {
        //给收集到的dpes数组打标记
        initDepMarkers(this)
      } else {
        //重置deps数组依赖,需要重新收集
        cleanupEffect(this)
      }
      // 执行fn
      return this.fn()
    } finally {
      if (effectTrackDepth <= maxMarkerBits) {
        // 执行完effect,看一下需要删除那些依赖添加哪些依赖
        finalizeDepMarkers(this)
      }
      //标记effect层级,执行完--effectTrackDepth
      trackOpBit = 1 << --effectTrackDepth

      activeEffect = this.parent
      shouldTrack = lastShouldTrack
      this.parent = undefined

      if (this.deferStop) {
        this.stop()
      }
    }
  }

  stop() {
    // stopped while running itself - defer the cleanup
    if (activeEffect === this) {
      this.deferStop = true
    } else if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

上面只是3.0之前的想法每当effect再次执行的时候,都要先将上一次收集过的清空掉,重新进行收集,这么做的目的其实是为了避免上一次收集到的依赖,本次不需要去收集的情况所导致的依赖收集错误

但是大部分场景中依赖的变动其实是相对较小的,并不需要如此大刀阔斧的进行全部清空,再次收集。

因此Vue进行了优化处理,通过位运算和给dep打标记的方式进行了优化

Effect我们需要做几个事情   (代码过于零散--建议直接从源码 track,reactiveEffect慢慢入手,或者 阅读  Vue3.2中reactivity的优化 - 掘金 (juejin.cn)

  1. 在effect执行前,先将effectTrackDepth++
  2. 将原本收集到的dep打上自己的标记,作为旧标记
  3. 执行期间通过track给dep.n打上新标记
  4. 执行结束开始对比dep.w 和 dep.n,整理依赖
  5. effectTrackDepth--
// dep 构造
export const createDep = (effects?: ReactiveEffect[]): Dep => {
  const dep = new Set<ReactiveEffect>(effects) as Dep
  dep.w = 0  //旧标记
  dep.n = 0  //新标记
  return dep
}
// effect的追踪深度(effect层级)
let effectTrackDepth = 0
// 位标记
export let trackOpBit = 1
// init 打上旧标记
export const initDepMarkers = ({ deps }: ReactiveEffect) => {
  if (deps.length) { //给deps打标记
    for (let i = 0; i < deps.length; i++) {
      deps[i].w |= trackOpBit // set was tracked
    }
  }
}
export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  let shouldTrack = false
  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      //dep.n 打上标记位
      dep.n |= trackOpBit // set newly tracked
      //检查dep.w旧标记有没有打过标记并赋值给shouldTrack
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // Full cleanup mode.
    // 全面清理
    shouldTrack = !dep.has(activeEffect!)
  }
  //TODO优化点(通过打标记)
  // 1.wasTracked(dep)--如果dep.w打过标记说明已经存过,没有则说明没存过
  // 2.通过dep.has(activeEffect)---观察依赖有无存在当前activeEffect中
  if (shouldTrack) {
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
    if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack(
        extend(
          {
            effect: activeEffect!
          },
          debuggerEventExtraInfo!
        )
      )
    }
  }
}

// 对比cleanupEffect进行了一定程度的优化
// 大部分场景对于effect的依赖改动变化很小,直接使用cleanupEffect等于清空重新进行依赖收集
// 通过提前标记旧的依赖最后通过对比明显效果好很多
export const finalizeDepMarkers = (effect: ReactiveEffect) => {
  const { deps } = effect
  if (deps.length) {
    let ptr = 0
    for (let i = 0; i < deps.length; i++) {
      const dep = deps[i]
      if (wasTracked(dep) && !newTracked(dep)) {
        // 之前收集到了这次没有,进入到dep中删除effect
        dep.delete(effect)
      } else { //保存dep
        deps[ptr++] = dep
      }
      // clear bits
      // 清除标记位--重置
      dep.w &= ~trackOpBit
      dep.n &= ~trackOpBit
    }
    deps.length = ptr
  }
}


// reactiveEffect的run方法
 run() {
    // 当active=false,执行函数但不收集依赖
    if (!this.active) {
      return this.fn()
    }
    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack

    //循环遍历parent的父级元素
    //直到找到了与当前实例this相同的父级元素,或者直到parent为null为止
    //避免在组件的父子关系中出现循环引用的情况
    while (parent) {
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    try {
      //将上一个activeEffect先保存起来
      this.parent = activeEffect
      // 将自己设置为当前活跃的 ReactiveEffect
      activeEffect = this
      shouldTrack = true
      //标记effect层级,执行一层effect,++effectTrackDepth
      trackOpBit = 1 << ++effectTrackDepth
      // 超过 maxMarkerBits 则 trackOpBit 的计算会超过最大整形的位数,降级为 cleanupEffect 
      if (effectTrackDepth <= maxMarkerBits) {
        //给收集到的dpes数组打标记
        initDepMarkers(this)
      } else {
        //重置deps数组依赖,需要重新收集
        cleanupEffect(this)
      }
      // 执行fn
      return this.fn()
    } finally {
      if (effectTrackDepth <= maxMarkerBits) {
        // 执行完effect,看一下需要删除那些依赖添加哪些依赖
        finalizeDepMarkers(this)
      }
      //标记effect层级,执行完--effectTrackDepth
      trackOpBit = 1 << --effectTrackDepth

      activeEffect = this.parent
      shouldTrack = lastShouldTrack
      this.parent = undefined

      if (this.deferStop) {
        this.stop()
      }
    }
  }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值