Vue Reactive源码🗣 computed

532 阅读3分钟

用法

有两种使用方法,一种只有get,其返回值也是只读的,另一种是可读可写

  1. get:传入一个getter函数,computed的返回值为getter函数返回值经过ref封装后,并且是只读的。
const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // error
  1. set: 也可以传入一个对象,包含get和set两个函数,这时,computed的返回值就可写了。

源码中关于可读可写的判断也是根据我们传入的参数来判断,我们可以看到,如果传入的是一个函数,默认就是getter函数,自然是个可读的,另外如果传入对象,这里注意 ,并不是传入函数那就是可写的,你也得有set函数,这在源码中也有提现:

// ...

 return new ComputedRefImpl(
    getter,
    setter,
    isFunction(getterOrOptions) || !getterOrOptions.set //这对应构造函数的readonly
  ) as any


constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean
  ) {
   //...
  }

通过函数重载来进行两种用法的类型校验:

// read-only
function computed<T>(getter: () => T): Readonly<Ref<Readonly<T>>>

// writable
function computed<T>(options: { get: () => T; set: (value: T) => void }): Ref<T>

用法不是很复杂,其实watchwatchEffect都类似,下面我们深入看一下返回这个Ref,也就是 ComputedRefImpl这个类

ComputedRefImpl

先贴一下源码:

class ComputedRefImpl<T> {
 // some initial var...

  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean
  ) {
   // .. 
  }

  get value() {
   // ...
  }

  set value(newValue: T) {
   // ... 
  }
}

里面定义了一些变量,除了构造函数还有get和set,里面还调用了effect、track、trigger方法,源码中专门放在一个文件effect.ts中,这里面是专门处理副作用的,包括对应依赖的存储

这里调用effect对getter进行包装,就是为了创建缓存,用文档的原话来说:计算属性是基于它们的反应依赖关系缓存的,计算属性只在相关响应式依赖发生改变时它们才会重新求值

 this.effect = effect(getter, {
      lazy: true,
      scheduler: () => {
        if (!this._dirty) {
          this._dirty = true
          trigger(toRaw(this), TriggerOpTypes.SET, 'value')
        }
      }

这里传入了两个配置项,一个lazy,一个scheduler,一开始我也不知道这个scheduler是干嘛的,但是这个单词翻译过来就是调度程序,所谓调度程序就是指定如何运行副作用函数的,另外里面还有一个trigger函数,来触发SET操作的,所以顾名思义就是一个执行set操作,另外通过_dirty这个变量来控制是否读取缓存还是新值

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

然后我们进入effect这个方法中查看,主要通过createReativeEffect这个函数来创建

function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
  // ....
  } as ReactiveEffect
  effect.id = uid++
  effect.allowRecurse = !!options.allowRecurse
  effect._isEffect = true
  effect.active = true
  effect.raw = fn
  effect.deps = []
  effect.options = options
  return effect
}

我们可以看到 返回一个reactiveEffect函数,这个函数上包括一下属性:

  _isEffect: true
  id: number
  active: boolean
  raw: () => T
  deps: Array<Dep>
  options: ReactiveEffectOptions
  allowRecurse: boolean

track & trigger

副作用的依赖项的数据结构大致如下:

weakmap:(target, Map(key,Set[effect,effect]) )

target----->key------>dep

track用于追踪收集依赖,trigger用于触发响应。