Contents

一、computed 函数通过内部的 ReactiveEffect 实现了对依赖项的追踪和更新,从而实现了计算属性的响应式特性

computed 接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值

它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象

  • 基本使用
1const count = ref(1)
2const plusOne = computed(() => count.value + 1)
3
4console.log(plusOne.value) // 2
5plusOne.value++ // error
1const count = ref(1)
2const plusOne = computed({
3  get: () => count.value + 1,
4  set: (val) => {
5    count.value = val - 1
6  }
7})
8
9plusOne.value = 1
10console.log(count.value) // 0

 

  • 实现原理 (packages/reactivity/src/computed.ts)

通过创建 ComputedRefImpl 的实例来实现计算属性的核心功能

1export function computed<T>(
2  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
3  debugOptions?: DebuggerOptions,
4  isSSR = false,
5) {
6  /// ...
7
8  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
9
10  /// ...
11
12  return cRef as any
13}

 

ComputedRefImpl 类的实现:通过 ReactiveEffect 来管理计算属性的更新逻辑,effect 是一个响应式的副作用,用于跟踪依赖并在依赖变化时触发重新计算

1export class ComputedRefImpl<T> {
2  public dep?: Dep = undefined
3
4  private _value!: T
5  public readonly effect: ReactiveEffect<T>
6
7  public readonly __v_isRef = true
8  public readonly [ReactiveFlags.IS_READONLY]: boolean = false
9
10  public _cacheable: boolean
11
12  /**
13   * Dev only
14   */
15  _warnRecursive?: boolean
16
17  constructor(
18    private getter: ComputedGetter<T>,
19    private readonly _setter: ComputedSetter<T>,
20    isReadonly: boolean,
21    isSSR: boolean,
22  ) {
23    /// ...
24    this.effect = new ReactiveEffect(
25      () => getter(this._value),
26      () =>
27        triggerRefValue(
28          this,
29          this.effect._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect
30            ? DirtyLevels.MaybeDirty_ComputedSideEffect
31            : DirtyLevels.MaybeDirty,
32        ),
33    )
34    /// ...
35  }
36
37  get value() {
38    /// ...
39  }
40
41  set value(newValue: T) {
42    /// ...
43  }
44
45  /// ...
46}
1export class ReactiveEffect<T = any> {
2  /// ...
3
4  constructor(
5    public fn: () => T,
6    public trigger: () => void,
7    public scheduler?: EffectScheduler,
8    scope?: EffectScope,
9  ) {
10    /// ...
11  }
12
13  /// ...
14
15  run() {
16    this._dirtyLevel = DirtyLevels.NotDirty
17    if (!this.active) {
18      return this.fn()
19    }
20    let lastShouldTrack = shouldTrack
21    let lastEffect = activeEffect
22    try {
23      shouldTrack = true
24      activeEffect = this
25      this._runnings++
26      preCleanupEffect(this)
27      return this.fn()
28    } finally {
29      postCleanupEffect(this)
30      this._runnings--
31      activeEffect = lastEffect
32      shouldTrack = lastShouldTrack
33    }
34  }
35
36  /// ...
37}

 

value 的获取:在获取 value 时,Vue 会检查计算属性是否需要重新计算(effect.dirty),如果需要,会调用 effect.run() 重新计算值,并触发相应的依赖追踪和更新

trackRefValue → trackEffect → ref.dep 依赖追踪

triggerRefValue → triggerEffects → effect.trigger 更新

1export class ComputedRefImpl<T> {
2  /// ...
3
4  get value() {
5    // the computed ref may get wrapped by other proxies e.g. readonly() #3376
6    const self = toRaw(this)
7    if (
8      (!self._cacheable || self.effect.dirty) &&
9      hasChanged(self._value, (self._value = self.effect.run()!))
10    ) {
11      triggerRefValue(self, DirtyLevels.Dirty)
12    }
13    trackRefValue(self)
14    if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
15      if (__DEV__ && (__TEST__ || this._warnRecursive)) {
16        warn(COMPUTED_SIDE_EFFECT_WARN, `\n\ngetter: `, this.getter)
17      }
18      triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
19    }
20    return self._value
21  }
22
23  /// ...
24}

 

value 的设置:如果计算属性是可写的,设置新值时会调用 setter 函数

1export class ComputedRefImpl<T> {
2  /// ...
3
4  set value(newValue: T) {
5    this._setter(newValue)
6  }
7
8  /// ...
9}

 

二、计算属性 vs 方法

计算属性的最大特点是它们会基于其依赖项缓存计算结果果。只有当依赖的响应式数据发生变化时,计算属性才会重新计算,否则会返回上一次的计算结果

方法每次被调用时,都会重新执行,不管依赖的数据是否发生了变化。这在某些情况下会导致不必要的重复计算,影响性能

 

三、其他

学习的记录,仅供参考