【Vue3 源码解析】computed

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,
  isSSR = false
) {
  // 格式化参数
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>

  // 如果传过来的是一个函数(即函数式写法),那么就是只读的
  const onlyGetter = isFunction(getterOrOptions)
  if (onlyGetter) {
    getter = getterOrOptions  // 我们传过来的函数赋值给 getter
    setter = __DEV__  // 不支持 setter, 否则会报错
      ? () => {
          console.warn('Write operation failed: computed value is readonly')
        }
      : NOOP
  } else {  // 选项式写法 可读可写
    getter = getterOrOptions.get  
    setter = getterOrOptions.set
  }

  // 将 getter 和 setter 传入这个类
  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)

  if (__DEV__ && debugOptions && !isSSR) {
    cRef.effect.onTrack = debugOptions.onTrack
    cRef.effect.onTrigger = debugOptions.onTrigger
  }

  return cRef as any
}

这段代码是 computed 函数内部的逻辑,用于根据传入的参数 getterOrOptions 创建计算属性的 gettersetter,然后创建一个 ComputedRefImpl 实例并返回。

以下是代码的详细解释:

  1. 首先,定义了 gettersetter 变量,用于后续存储计算属性的获取和设置函数。

  2. 通过检查 getterOrOptions 是否为函数来判断是函数式写法还是选项式写法。函数式写法表示计算属性是只读的,因此 getterOrOptions 直接赋值给 getter 变量,而 setter 变量则被设置为一个函数。这个函数用于在开发环境下报错,提示用户不能对只读的计算属性进行写入操作。

    // 只读的计算属性,直接使用传入的 getter 函数
    if (onlyGetter) {
      getter = getterOrOptions;  // 传入的函数作为 getter
      setter = __DEV__  // 如果是开发环境,设置 setter 为一个警告函数
        ? () => {
            console.warn('Write operation failed: computed value is readonly')
          }
        : NOOP;  // 否则设置为空函数 NOOP
    }
    
  3. 如果不是函数式写法,表示计算属性是可读可写的。此时,从传入的 getterOrOptions 对象中提取 getset 函数,分别赋值给 gettersetter 变量。

    } else {  // 选项式写法,可以读写
      getter = getterOrOptions.get;  // 从选项对象中提取 getter
      setter = getterOrOptions.set;  // 从选项对象中提取 setter
    }
    
  4. 创建 ComputedRefImpl 实例 cRef,将 gettersetter、只读标志以及 isSSR 参数传入。cRef 代表了最终的计算属性对象。

    // 创建 ComputedRefImpl 实例
    const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR);
    
  5. 在开发环境下,如果传入了调试选项 debugOptions 并且不是服务端渲染(isSSRfalse),则将调试选项中的 onTrackonTrigger 回调函数分别赋值给 cRef.effect.onTrackcRef.effect.onTrigger,用于跟踪计算属性的依赖和触发情况。

    if (__DEV__ && debugOptions && !isSSR) {
      cRef.effect.onTrack = debugOptions.onTrack;
      cRef.effect.onTrigger = debugOptions.onTrigger;
    }
    
  6. 最后,将创建的 cRef 对象返回,它包含了计算属性的值、获取和设置函数等信息。

    return cRef as any;
    

总之,这段代码是 computed 函数内部的逻辑,用于根据传入的参数创建计算属性的 gettersetter 函数,并最终创建一个 ComputedRefImpl 实例,返回计算属性对象。根据不同的写法和配置,可以创建只读或可读可写的计算属性。

然后我们具体看一下 ComputedRefImpl 实例:

export class ComputedRefImpl<T> {
  public dep?: Dep = undefined

  private _value!: T
  public readonly effect: ReactiveEffect<T>

  public readonly __v_isRef = true
  public readonly [ReactiveFlags.IS_READONLY]: boolean = false

  public _dirty = true  // 看是否是脏的,是否需要重新计算
  public _cacheable: boolean

  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean,
    isSSR: boolean
  ) {
    // 创建 ReactiveEffect 对象,用于管理计算属性的响应式依赖
    // 依赖变化并且脏值为 false ,依赖变化这个函数才会执行
    this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true
        triggerRefValue(this)
      }
    })

    // 将计算属性与 ReactiveEffect 对象关联
    this.effect.computed = this
    this.effect.active = this._cacheable = !isSSR // 根据是否在服务器端渲染设置是否缓存计算结果
    this[ReactiveFlags.IS_READONLY] = isReadonly // 标记是否为只读计算属性
  }

  // 收到脏值为 true ,直接执行 get value 函数
  get value() {
    // 计算属性的获取方法,用于获取计算属性的值
    const self = toRaw(this) // 获取原始的计算属性对象,脱离 Proxy 代理
    trackRefValue(self) // 跟踪计算属性的引用
    if (self._dirty || !self._cacheable) {
      // 如果属性标记为脏(需要重新计算)或者不可缓存,则重新计算属性值
      self._dirty = false
      self._value = self.effect.run()! // 运行计算属性的 effect 函数以计算新值
    }
    return self._value
  }

  set value(newValue: T) {
    // 计算属性的设置方法,用于设置计算属性的值
    this._setter(newValue) // 调用用户提供的 setter 函数
  }
}

这段代码定义了 ComputedRefImpl 类,该类是 Vue 3 中计算属性(computed)的实现的一部分。

【Vue3 源码解析】computed_第1张图片

对代码的主要解释如下:

  1. ComputedRefImpl 类用于实现计算属性。它接受以下参数:

    • getter: 计算属性的获取函数,用于计算属性的值。
    • _setter: 计算属性的设置函数,用于设置计算属性的值。
    • isReadonly: 一个布尔值,表示计算属性是否只读。
    • isSSR: 一个布尔值,表示是否在服务器端渲染。
  2. 在构造函数中,创建了一个 ReactiveEffect 对象,该对象将 getter 函数传递给它,并定义了一个回调函数。当计算属性的依赖发生变化时,这个回调函数将被触发。回调函数会将 _dirty 属性设置为 true,表示计算属性需要重新计算,并调用 triggerRefValue(this),通知相关依赖进行更新。

  3. 将创建的 ReactiveEffect 对象与当前的 ComputedRefImpl 实例关联,以便后续进行管理和跟踪。

  4. 根据传入的参数,设置 effectactive 属性和 _cacheable 属性。active 表示是否需要在计算属性失活时停止计算,_cacheable 表示是否可以缓存计算结果。

  5. 定义 get value() 方法,用于获取计算属性的值。在获取属性值之前,会调用 trackRefValue(self) 来跟踪计算属性的引用。如果计算属性被标记为脏(需要重新计算)或者不可缓存,将重新计算属性值,并将 _dirty 设置为 false,以表示属性值已经计算完毕。

  6. 定义 set value(newValue: T) 方法,用于设置计算属性的值。它会调用用户提供的 _setter 函数来进行属性值的设置。

总之,这段代码实现了计算属性的核心逻辑,包括属性值的获取和设置,以及属性值的计算和缓存管理。计算属性可以根据其依赖自动更新,并且可以选择是否设置为只读。

你可能感兴趣的:(Vue3,源码解析,前端,vue.js,javascript)