Vue 3 Pre-Alpha / vue-next 源码学习之 vue/reactivity/computed.ts

前言

前一篇《Vue 3 Pre-Alpha / vue-next 源码学习之 vue/reactivity/effect.ts》中,我们学习了用于响应数据读写变化的方法(在 vue 3 中会把用户传入的响应方法/响应函数再次封装而成为 effect 或者叫 ReactiveEffect),现在我们来介绍一种依赖于一种依赖于 effect 的扩展于 Ref 的响应式数据类型 computedComputedRef),相信用过 vue 的人都大概会猜到 computed 应该于 vue 1.x / vue 2. x 中的 computed 有很大的关联。没错,用法与功能大概一样。

源码分析

注意带【】符号的为关键代码,关键代码能体现作者的最关键最基础的实现思路

import { effect, ReactiveEffect, effectStack } from './effect' // couputed 依赖于 effect 下面可以看到
import { Ref, UnwrapRef } from './ref' // couputed 其实是 extends 于 Ref 下面可以看到
import { isFunction, NOOP } from '@vue/shared' // 工具函数,判断是不是函数与返回一个空对象

export interface ComputedRef<T> extends WritableComputedRef<T> { // 构造函数 computed 返回为 ComputedRef / WritableComputedRef / any
  readonly value: UnwrapRef<T>  // 比普通的 Ref 多了一个 readonly 的 value 值为解套后的值
}

export interface WritableComputedRef<T> extends Ref<T> { // 比普通的 Ref 多了一个 readonly 的 effect 的 Ref
  readonly effect: ReactiveEffect<T>
}

export type ComputedGetter<T> = () => T // 带返回值的 getter
export type ComputedSetter<T> = (v: T) => void // 不带返回值的 setter

export interface WritableComputedOptions<T> { // computed 的参数接口函数定义
  get: ComputedGetter<T>
  set: ComputedSetter<T>
}

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>
): any {
  const isReadonly = isFunction(getterOrOptions)
  const getter = isReadonly
    ? (getterOrOptions as ComputedGetter<T>) // 只传一个 getter
    : (getterOrOptions as WritableComputedOptions<T>).get // 同时传了 getter 及 setter
  const setter = isReadonly
    ? __DEV__
      ? () => {
          console.warn('Write operation failed: computed value is readonly')
        }
      : NOOP
    : (getterOrOptions as WritableComputedOptions<T>).set

  let dirty = true // 私有变量,数据是还变化
  let value: T // 私有变量,用来存放值

  const runner = effect(getter, { // 使用effect把 getter 封装成 ReactiveEffect,reactive / Ref 对象与 effect 是通过 targetMap 来建立绑定关系的
    lazy: true, // 异步触发执行
    // mark effect as computed so that it gets priority during trigger
    computed: true, // 标记为 true,优先级提升,优化触发
    scheduler: () => { // 把 dirty 设为 true,开始时设置数据为变更状态
      dirty = true
    }
  })
  return { // 【computed 的关键代码】 返回一个比普通的 Ref 多了一个 readonly 的 effect 的 Ref,主要也是通过 get set 实现
    _isRef: true,
    // expose effect so computed can be stopped
    effect: runner,
    get value() {
      if (dirty) {
        value = runner()
        dirty = false
      }
      // When computed effects are accessed in a parent effect, the parent
      // should track all the dependencies the computed property has tracked.
      // This should also apply for chained computed properties.
      trackChildRun(runner) // 如果有父 effect 与之关联,所有的 parentRunner 也要被 track
      return value
    },
    set value(newValue: T) {
      setter(newValue)
    }
  }
}

function trackChildRun(childRunner: ReactiveEffect) {
  if (effectStack.length === 0) {
    return
  }
  const parentRunner = effectStack[effectStack.length - 1]
  for (let i = 0; i < childRunner.deps.length; i++) {
    const dep = childRunner.deps[i]
    if (!dep.has(parentRunner)) {
      dep.add(parentRunner)
      parentRunner.deps.push(dep)
    }
  }
}

总结

通过源码我们可以知道,computed 扩展于 Ref,也是一种 Ref,跟 Ref 一样主要也是通过 get set  附加上带 getter 函数的 effect 来实现监听响应式的能力。下面通过两个测试用例来验证:

  it('should return updated value or not', () => {
    const value = reactive<{ foo?: number }>({})
    const cValue = computed(() => value.foo)
    expect(cValue.value).toBe(undefined)
    value.foo = 1
    expect(cValue.value).toBe(1) // 值跟着变化,因为响应变化的能力来源于 reactive

    const n = ref(1)
    const plusOne = computed(() => n.value + 1)
    expect(plusOne.value).toBe(2)
    n.value = 2
    expect(plusOne.value).toBe(3) // 值跟着变化,因为响应变化的能力来源于 ref

    const plainObject = { foo: 0 }
    const cPOValue = computed(() => plainObject.foo)
    expect(cPOValue.value).toBe(0)
    plainObject.foo = 1
    expect(cPOValue.value).toBe(0) // 值不变,因为 computed 的不是响应式的 Ref / reactive,而是 plain Object
  })

 

作者: 博主

Talk is cheap, show me the code!

发表评论

邮箱地址不会被公开。

Captcha Code