先来回忆下计算的简单使用方法

使用getter函数

computed(() => count.value + 1)

使用具有 get 和 set 函数的对象

const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>探究计算属性</title>
</head>
<body>
  <div id="app"></div>
  <script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
  <script>
    const { reactive, effect, computed } = VueReactivity
    const state = reactive({
      count: 1
    })

    // 当依赖值发生变化的时候会重新执行计算属性
    const calCount = computed(() => state.count + 1)

    console.log(calCount);

    effect(() => {
      app.innerHTML = calCount.value
    })

    setTimeout(() => {
      state.count += 5
    }, 1000)
  </script>
</body>
</html>

控制台输出

下面是源代码的一个简版,具体代码可以看官方github源代码 (opens new window)

import { Ref } from './ref'
import { effect, ReactiveEffect, track, trigger } from './effect'
import { TriggerOpTypes, TrackOpTypes } from './operations'
import { isFunction, NOOP } from '../shared/index'

const __DEV__ = 1

export interface WritableComputedRef<T> extends Ref<T> {
  readonly effect: ReactiveEffect<T>
}
export interface ComputedRef<T = any> extends WritableComputedRef<T> {
  readonly value: T
}
export type ComputedGetter<T> = (ctx?: any) => T
export type ComputedSetter<T> = (v: T) => void
export interface WritableComputedOptions<T> {
  get: ComputedGetter<T>
  set: ComputedSetter<T>
}

class ComputedRefImpl<T> {
  private _value!: T
  private _dirty = true

  public readonly effect: ReactiveEffect<T>

  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean
  ) {
    this.effect = effect(getter, {
      lazy: true,
      scheduler: () => {
        if (!this._dirty) {
          this._dirty = true
          trigger(this, TriggerOpTypes.SET, 'value')
        }
      }
    })
  }

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

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

export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
export function computed<T>(
  options: WritableComputedOptions<T>
): WritableComputedRef<T>
export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>
  
  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
    setter = __DEV__
    ? () => {
        console.warn('Write operation failed: computed value is readonly')
      }
    : NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }

  return new ComputedRefImpl(
    getter,
    setter,
    isFunction(getterOrOptions) || !getterOrOptions.set
  ) as any
}