【Vue3源码学习】响应式api: computed

  • Post author:
  • Post category:vue




computed的基本使用


computed

是组件的计算属性,它的含义是依赖于其他状态而生成的状态,与响应式紧密相关。


computed

有两种创建方式:



  1. computed

    函数传递一个

    getter

    方法创建

    immutable reactive ref object

    const count = ref(1)
    const plusOne = computed(() => count.value + 1)
    console.log(plusOne.value) // 2
    plusOne.value = 3 // error,因为plusOne是immutable ref obj
    


  2. computed

    函数传递一个有

    get



    set

    方法的对象来创建一个 writable ref object

    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
    



computed原理


computed

的特性就在于能够缓存计算的值(提升性能),只有当

computed

的依赖发生变化时才会重新计算,否则读取

computed

的值则一直是之前的值。

computed

是怎么做到的呢?让我们一起来看看源码来解惑。



computed源码

源码地址:

packages\reactivity\src\computed.ts



computed创建


computed

函数接收一个

getter

方法或者是一个含有

get

方法和

set

方法的对象,并返回一个

ref

对象。

export function computed<T>(
  getter: ComputedGetter<T>,
  debugOptions?: DebuggerOptions
): ComputedRef<T>
export function computed<T>(
  options: WritableComputedOptions<T>,
  debugOptions?: DebuggerOptions
): WritableComputedRef<T>
export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
  debugOptions?: DebuggerOptions
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>

  const onlyGetter = isFunction(getterOrOptions)
  // 当getterOrOptions为函数的时候,说明是只读computed,会将其赋值给与getter
  if (onlyGetter) {
    getter = getterOrOptions
    setter = __DEV__
    ? () => {
      console.warn('Write operation failed: computed value is readonly')
    }
    : NOOP
  } else {
    // 当getterOrOptions为对象的时候,说明是是自定义的getter setter,会将set和get分别赋值给setter,getter。
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }

  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter)

  if (__DEV__ && debugOptions) {
    cRef.effect.onTrack = debugOptions.onTrack
    cRef.effect.onTrigger = debugOptions.onTrigger
  }
  return cRef as any
}



ComputedRefImpl产生ref对象

通过上面的代码我们可以看出

computed

函数返回的ref对象是执行构造方法

ComputedRefImpl

而创建的一个实例。

ComputedRefImpl

的构造方法一共做了两件事:

  • 调用

    effect

    方法生成

    watcher

    监听函数并赋值给实例的 effect 属性。
  • 设置ref对象是否为

    readonly

class ComputedRefImpl<T> {
  public dep?: Dep = undefined
  private _value!: T
  private _dirty = true  // 脏数据flag 用来判断是否需要重新计算
  public readonly effect: ReactiveEffect<T> 

  public readonly __v_isRef = true // ref响应式对象标识
  public readonly [ReactiveFlags.IS_READONLY]: boolean // ReactiveFlags只读标识

  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean
  ) {
    // 调用 ReactiveEffect 方法生成监听函数并赋值给实例的 effect 属性
    this.effect = new ReactiveEffect(getter, () => {
      // 由于初始化时,计算过一次computed值,_dirty已经设为了false
      // 所以当内部依赖发生变化时,会由此进入,设置_dirty为true,这样获取computed值时会再次计算
      if (!this._dirty) {
        this._dirty = true
        triggerRefValue(this)
      }
    })
    // 设置ref对象是否为 readonly
    this[ReactiveFlags.IS_READONLY] = isReadonly
  }

  get value() {
    // the computed ref may get wrapped by other proxies e.g. readonly() #3376
    const self = toRaw(this)
    trackRefValue(self) // 依赖收集
    // 初始化时,_dirty为true,会计算一次computed值
    if (self._dirty) {
      // 设置_dirty为false, 防止再次获取时重新计算,这就是 computed 缓存值的实现原理
      self._dirty = false
      self._value = self.effect.run()!
    }
    return self._value
  }

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



computed

缓存值的实现分析

我们发现声明一个

computed

时其实并不会执行getter方法,只有在读取

computed

值时才会执行它的

getter

方法,而这个方法实在构造函数

ComputedRefImpl



getter

方法定义实现的。


getter

方法会在读取

computed

值的时候执行(包含依赖收集),由于脏数据的开关,初始化时

_dirty

被设为

true

) ,在

getter

方法中会计算一遍

computed

的值并设置

self._dirty = false

,在数据源不发生变化的情况下之后再获取

computed

的值时由于

_dirty



false

就不会重新计算。这就是

computed

缓存值的实现原理。


computed重新计算值

在数据源发生变化时,在

ComputedRefImpl

构造函数里为对象添加的

effect

函数会给对象的响应式对象生成监听函数,并对

scheduler

进行了设置。

所以,当

computed

内部依赖的状态发生改变,执行对应的监听函数,这其中自然会执行

scheduler

里的操作。而在

scheduler

中将

_dirty

设为了

true

。从而在下次取值时,进行重新计算。



结语

如果本文对你有一丁点帮助,点个赞支持一下吧,感谢感谢



版权声明:本文为qq_38987146原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。