【Vue3源码分析】computed

364 阅读1分钟

computed声明方式方式一: read-only

function computed<T>(getter: () => T): Readonly<Ref<Readonly<T>>>

比如:

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // error

源码

export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>

computed声明方式方式二: writable

export interface ComputedRef<T = any> extends WritableComputedRef<T> {
  readonly value: T
}

export interface WritableComputedRef<T> extends Ref<T> {
  readonly effect: ReactiveEffect<T>
}

function computed<T>(options: { get: () => T; set: (value: T) => void }): Ref<T>
export function computed<T>(options: WritableComputedOptions<T>):WritableComputedRef<T>

比如:

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

源码

export type ComputedGetter<T> = (ctx?: any) => T
export type ComputedSetter<T> = (v: T) => void

export interface WritableComputedOptions<T> {
  get: ComputedGetter<T>
  set: ComputedSetter<T>
}

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  //根据传入是否是函数来判断是否可读
  ) {
  //调用effect函数,并且lazy为true,也就是懒加载
    this.effect = effect(getter, {
      lazy: true,
      scheduler: () => {
      //set,第一次_dirty是false,因为get中改变的,调用trigger
      //触发trigger,把自己加入依赖
        if (!this._dirty) {
          this._dirty = true
          trigger(toRaw(this), TriggerOpTypes.SET, 'value')
        }
      }
    })

    this[ReactiveFlags.IS_READONLY] = isReadonly
  }
// this.effect()相当于vue2中的get(),收集依赖的过程
// _dirty 是为了lazy watchers
// Vue2中:watcher初始化会立即调用一次watcher.get方法,然后实际上可以通过传入{lazy:true}参数来延迟watcher.get方法的执行
  get value() {
  // 默认_dirty为true,数据是脏的,所以第一次会触发,调用effect()获取值,然后再_dirty变成false,触发track()函数
    if (this._dirty) {
      this._value = this.effect()
      this._dirty = false
    }
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newValue: T) {
    this._setter(newValue)
  }
}
// 如果传入的是一函数,则仅仅是get,也就是可读不可写,否则是带有get和set的对象
export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>

  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
    setter = __DEV__
      ? () => {
          console.warn('Write operation failed: computed value is readonly')
        }
      : NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }

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

从源码可以很清楚看到,使用具有 get 和 set 函数的对象来创建可写的 ref 对象。

get是触发track函数; set是触发trigger函数;