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

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 函数传递一个有 getset 方法的对象来创建一个 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 方法,而这个方法实在构造函数 ComputedRefImplgetter 方法定义实现的。

getter 方法会在读取 computed 值的时候执行(包含依赖收集),由于脏数据的开关,初始化时 _dirty 被设为 true) ,在 getter 方法中会计算一遍computed 的值并设置self._dirty = false,在数据源不发生变化的情况下之后再获取 computed 的值时由于 _dirtyfalse 就不会重新计算。这就是 computed 缓存值的实现原理。

computed重新计算值

在数据源发生变化时,在ComputedRefImpl构造函数里为对象添加的effect 函数会给对象的响应式对象生成监听函数,并对 scheduler 进行了设置。
所以,当computed 内部依赖的状态发生改变,执行对应的监听函数,这其中自然会执行 scheduler 里的操作。而在 scheduler 中将 _dirty 设为了 true 。从而在下次取值时,进行重新计算。

结语

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

猜你喜欢

转载自blog.csdn.net/qq_38987146/article/details/123199598