走进Vue3源码:computed

801 阅读3分钟

概述

从一个 demo 入手来讲解 computed 的原理, 阅读本文前可先了解 走进Vue3源码:响应式原理(超详细)

Demo

import { reactive, computed, watchEffect } from 'vue'
export default{
  setup() {
    const state = reactive({ count: 1 })
    
    // 1. 初始化 computed
    const doubleCount= computed(() => state.count * 2)
    
    // 2. 触发 doubleCount 的 get, 从而收集 doubleCount 的依赖和 state.count 的依赖
    watchEffect(() => console.log(doubleCount.value))
    // 输出 2
    
    // 3. 触发 state.count 的 set,从而触发 state.count 的依赖执行和 doubleCount 的依赖执行
    state.count ++
    // 输出 4
}

1. 初始化 computed

执行代码
const doubleCount= computed(() => state.count * 2)
源码详解:computed.ts & effect.ts
// 1. 执行 computed
export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>
  // 1. 格式化 setter 和 getter, 因为 computed 支持函数和对象两种传参
  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
    setter = __DEV__
      ? () => {
          console.warn('Write operation failed: computed value is readonly')
        }
      : NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }
  // 2. 构建 ComputedRef
  return new ComputedRefImpl(
    getter,
    setter,
    isFunction(getterOrOptions) || !getterOrOptions.set
  ) as any
}

// 2. 生成 ComputedRef
class ComputedRefImpl<T> {
  private _value!: T
  private _dirty = true
  public readonly effect: ReactiveEffect<T>
  public readonly __v_isRef = true;
  public readonly [ReactiveFlags.IS_READONLY]: boolean

  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean
  ) {
    // 1. 生成 effect, effect 可以看做是 demo 中 getter: () => state.count * 2 方法的代理方法,便于处理依赖
    //    当该计算属性被作为依赖执行时(demo 中 doubleCount 的 effect 作为 state.count 的依赖),实际是执行 effect,而非原先传入的 getter
    this.effect = effect(getter, {
      lazy: true,
      scheduler: () => {
        ...
      }
    })

    this[ReactiveFlags.IS_READONLY] = isReadonly
    // 初始化完成
  }
 get value() { ... }
 set value() { ... }
}

2. 收集计算属性 doubleCount 的依赖和 state.count 的依赖

执行代码
// 触发 doubleCount 的 get 
// 将下面的 watchEffect 对应的 effect 添加到 doubleCount 的依赖中
// 也就是说当 doubleCount 的值改变时,watchEffect 对应的 effect 会被执行
watchEffect(() => console.log(doubleCount.value))
源码详解:computed.ts
class ComputedRefImpl<T> {
  ...

  get value() {
    // 1. 懒执行获取 doubleCount 的值 
    if (this._dirty) {  // this._dirty 初始值为 true
      // 2. 在执行下面 this.effect() 的同时会触发 state.count 的 get
      //    因此会将 doubleCount effect 添加到 state.count 的依赖中

      this._value = this.effect()
      this._dirty = false
    }
    // 2. 将 watchEffect 的 effect 添加到 doubleCount 的依赖中
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
    // 依赖收集结束,此时三者关系: watchEffect(effect) 是 doubleCount 的依赖, doubleCount(effect) 是 state.count 的依赖 
  }
}

3. 触发 state.count 的依赖执行和 doubleCount 的依赖执行

执行代码
state.count ++ 
// 触发流程: 修改 state.count => 执行 doubleCount 的 effect => 执行 watchEffect 的 effct
源码详解:computed.ts
// 1. 触发 state.count 的 set,从而 state.count 的依赖会被执行,即 doubleCount 的 effect

class ComputedRefImpl<T> {

  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean
  ) {
    // 接上面步骤 1
    this.effect = effect(getter, {
      lazy: true,
      scheduler: () => {
        // 2. 执行 doubleCount 的 effect,实际上是执行 scheduler 方法,具体实现在 trigger 方法中
        if (!this._dirty) {
          // 2.1 标记为脏数据,表示需要更新
          this._dirty = true
          // 2.2 触发 doubleCount 的依赖执行,即 watchEffect 的 effect
          trigger(toRaw(this), TriggerOpTypes.SET, 'value')
        }
      }
    })

    this[ReactiveFlags.IS_READONLY] = isReadonly
  }
  
  // 3. watchEffect 对应的 effct 再次执行,并触发 doubleCount 的 get
  get value() {
    // 3.1 再次更新 doubleCount 的值,依旧是懒执行,在获取值时才会更新,有利于优化性能
    if (this._dirty) {
      this._value = this.effect()
      this._dirty = false
    }
    // 3.2 再次收集依赖不会生效,因为已收集过了
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }
}

总结

以上就是 computed 的实现原理及处理流程。
需要理清楚 demo 中 watchEffect, doubleCount, state.count 三者的关系:
watchEffect (依赖 =>) doubleCount (依赖 =>) state.count
修改 state.count (=> 触发) doubleCount 对应的 effect (=> 触发) watchEffect 对应的 effect