likes
comments
collection
share

[Vue 源码] Vue 3.2 - Computed 原理

作者站长头像
站长
· 阅读数 8

代码运行结果

[Vue 源码] Vue 3.2  - Computed 原理

代码示例

  <script src="./dist/reactivity.global.js"></script>
  <body>
    <div id="app"></div>
    <script>
      let { effect, reactive, ref, shallowRef, toRef, computed } = VueReactivity


      let state = reactive({ firstName: 'Tom', lastName: 'Benjamin' }) // 将普通的类型 转化成一个对象,这个对象中有value属性 指向原来的值

      const fullName = computed({
        // getter
        get() {
          return state.firstName + ' ' + state.lastName
        },
        // setter
        set(newValue) {
          ;[state.firstName, state.lastName] = newValue.split(' ')
        }
      })

      effect(() => {
        app.innerHTML = fullName.value
      })

      setTimeout(() => {
        fullName.value = 'Cyan Benjamin'
      }, 2000)

挂载阶段

第一:第一篇文章详细介绍了 reactive 创建响应式流程,我们这里默认已经创建好了响应式对象 obj。 [Vue 源码] Vue 3.2 - Reactive 原理

执行 第二句代码:

      const fullName = computed({
        // getter
        get() {
          return state.firstName + ' ' + state.lastName
        },
        // setter
        set(newValue) {
          ;[state.firstName, state.lastName] = newValue.split(' ')
        }
      })

第二:调用 computed 函数, 在 computed 函数中初始化 getter 和 setter函数,然后通过 new ComputedRefImpl 创建 computedRef 对象。

export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
  debugOptions?: DebuggerOptions,
  isSSR = false
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>

  getter = getterOrOptions.get
  setter = getterOrOptions.set

  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
  return cRef as any
}

第三: 执行 ComputedRefImpl 的 constructor 方法

  • 创建 ReactiveEffect 对象 赋值给 computed 对象的 effect 属性。创建过程 [Vue 源码] Vue 3.2 - Reactive 原理 有详细说明。
  • 默认创建好了 ReactiveEffect 对象 后 将 computedRef 的 getter 作为 ReactiveEffect 对象对象的 fn 属性。
  • 创建ReactiveEffect 对象时 传入第二个参数 -> scheduler 函数。
  • 初始化 dirry 属性 false 来表示是否为脏数据。
  • 返回 computed 对象

dirty 表示是否为脏数据来缓存计算属性,第一次或者说默认取值是脏数据执行 effect.run 方法取值,二次/多次取值 dirty 不是脏数据, 取缓存数据,等待依赖变了 scheduler 将 dirty 置为 true, 下次取值会重新执行 effect.run 方法来计算缓存值。

  public _dirty = true
  public _cacheable: boolean

  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean,
    isSSR: boolean
  ) {
    this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true
        triggerRefValue(this)
      }
    })
    
    this.effect.computed = this
    this.effect.active = this._cacheable = !isSSR
    this[ReactiveFlags.IS_READONLY] = isReadonly
  }

第三句代码调用:effect(() => {app.innerHTML = fullName.value}) 第四:

  • effect 函数调用,创建 ReactiveEffect 对象,执行 ReactiveEffect 对象上的 Run 方法,将 activeEfffect 对象只为当前创建的ReactiveEffect 对象,清除上一次的依赖列表, 执行 () => {app.innerHTML = fullName.value}
  • 执行 fullname.value 触发 fullname.value 的 getter 函数。
  get value() {
    trackRefValue(self)
    if (self._dirty || !self._cacheable) {
      self._dirty = false
      self._value = self.effect.run()!
    }
    return self._value
  }
  • getter 函数中 调用 trackRefValue(self)。
    • trackRefValue 中调用 trackEffects 函数,使用 computedRef 对象的 dep 属性来收集依赖对象。也就是将 activeEffect 对象也就是 effect 产生的 ReacttiveEffects 传入到 computedRef 对象的依赖对象列表当中去。
    • 执行
export function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
    ref = toRaw(ref)
    if (__DEV__) {
      trackEffects(ref.dep || (ref.dep = createDep()), {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      trackEffects(ref.dep || (ref.dep = createDep()))
    }
  }
}
  • getter 函数中 调用 self.effect.run()。也就是调用 computed 的 getter 或者说 ReactiveEffects 对象的 fn属性,也就是 get() { return state.firstName + ' ' + state.lastName }

  • 执行 obj.firstName 和 obj.lastName 触发 obj 的 getter ,将当前 computed 的 ReactEffect 对象收集到 firstName, lastName 属性的 effects Set 集合当中。通过 Reflect.get 方法得到值。

const targetMap  = {
{ firstName: 'Tom', lastName: 'Benjamin' }: {
    firstName: [ReactEffect],
    lastName: [ReactEffect]
}
}
  • 返回计算值赋值给 computed 对象的 _value 属性。

至此初次渲染完毕。

更新阶段

第一:fullname.value = "Cyan Benjamin", 触发 computedRef 对象 value 属性的 setter 方法,调用 this.settter 函数,传入新值。

  set value(newValue: T) {
    this._setter(newValue)
  }

第二:

  • setter 函数中通过 Reflect.set 给响应式对象赋值
  • 触发响应式对象 的 setter 操作, 通过 Reflect.set() 修改 lastname 和 firstname。
  • 继而触发 trigger 操作,不过这一次并没有执行该 ReactiveEffect 对象的run 方法,而是优先执行了 scheduler 方法。
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }

第三:scheduler 方法中执行 triggerRefValue ,

var scheduler = () => {
      if (!this._dirty) {
        this._dirty = true
        triggerRefValue(this)
      }
    }
  • 在 triggerRefValue 中 调用了 triggerEffects 函数,trigerEffects 执行了 triggerEffect 函数。

  • triggerEffect 函数中遍历 computedRef 对象的依赖列表。

  • 依次然后调用依赖对象的 run 方法

  • run 方法中 先通过 cleanEffect 函数清空依赖。 然后让 activeEffect 指向当前的 ReactiveEffect 对象(computed 创建的 ReactiveEffect 对象), 执行依赖对象的 fn 函数,也就是 getter 方法, 也就是 get() { return state.firstName + ' ' + state.lastName }

  • getter 方法中拿到最新值,同时 stata.firstName , state.lastName 又一次触发了 响应式对象 触发 getter 操作, 再一次跟踪activeEffect 依赖对象(computed 创建的 ReactiveEffect 对象)。

  • 返回并渲染最新值,等待下一次更新。

自此更新完毕