Vue3 Computed

2,188 阅读1分钟

computed

const count = ref(1) 

// 接受一个 getter 函数,并为从 getter 返回的值返回一个不变的响应式
const a = computed(() => count.value + 1)

// 也可以使用具有 `get` 和 `set` 函数的对象来创建可写的 ref 对象
const b = computed({ 
  get: () => count.value + 1, 
  set: val => { count.value = val - 1 } 
})
function computed(getterOrOptions) {
  let getter
  let setter
  
  if (isFunction(getterOrOptions)) {
    // 一个 getter 函数,这种 computed 是只读的
    getter = getterOrOptions
    setter = __DEV__? () => {
      console.warn('Write operation failed: computed value is readonly')
    }
    : NOOP
  } else {
    // 设置了 getter 和 setter 函数,这种 computed 是可写的
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }

  return new ComputedRefImpl(
    getter,
    setter,
    isFunction(getterOrOptions) || !getterOrOptions.set
  )

}

ComputedRefImpl

class ComputedRefImpl<T> {
  private _value
  private _dirty = true

  public readonly effect

  public readonly __v_isRef = true;
  public readonly [ReactiveFlags.IS_READONLY]

  constructor(getter, private readonly _setter, isReadonly) {
    // lazy延迟执行,不会立即执行 getter
    // scheduler 在派发时执行 getter,并且派发 computed 的所有依赖通知 computed 被修改
    this.effect = effect(getter, {
      lazy: true,
      scheduler: () => {
        if (!this._dirty) {
          this._dirty = true
          trigger(toRaw(this), TriggerOpTypes.SET, 'value')
        }
      }
    })

    this[ReactiveFlags.IS_READONLY] = isReadonly
  }

  get value() {
    const self = toRaw(this)
    if (self._dirty) {
      self._value = this.effect()
      self._dirty = false
    }
    track(self, TrackOpTypes.GET, 'value')
    return self._value
  }

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

computed 触发流程

<div> {{ b }} </div>
let a = ref(1)
// computed b 中访问 a 时,a 的 proxy.get 就会收集 b 的依赖
let b = computed(()=>{ return a + 1 })

a.value =2 
// 派发该 a 下的所有依赖
if (effect.options.scheduler) {
   effect.options.scheduler(effect)
} else {
   effect()
}
stateDiagram-v2
a.value=2 --> a.value
a.value --> 派发依赖b
派发依赖b --> b
b --> 派发依赖模板b
派发依赖模板b --> 模板b

模板b --> 访问b
访问b --> b
b--> 执行getter
执行getter--> 访问a
访问a --> a.value

依赖触发流程也与Vue2.5.17后的一致

ComputedRefImpl : {
  // 依赖
  effect:reactiveEffect(),
  // 响应式数据
  __v_isReadonly: true,
  // ref
  __v_isRef: true,
  // 是否使用缓存
  _dirty: false,
  // 上一次执行的参数
  _value:''
}
  1. 延时计算,只有当我们访问计算属性的时候,它才会执行 computed getter 函数计算;

  2. 缓存,它的内部会缓存上次的计算结果 value。而且只有使用的响应式变量改变与第一次访问, dirty 为 true 时才会重新计算。如果访问计算属性时 dirty 为 false,那么直接返回这个 value。

  3. 由于 computed 这种巧妙的派发机制,只要你不循环引用,无论嵌套多少层都是可以的。