vue3 Computed实现原理

在 Vue 3 中, computed 是一个用于定义和计算响应式计算属性的函数。它可以根据依赖的响应式数据进行自动更新,并且具有缓存机制,只有当依赖的数据发生变化时,才会重新计算计算属性的值。使用computd函数定义计算属性时,需要传入一个计算函数,该函数返回计算属性的值。Vue 3 内部会创建一个专门用于计算属性的响应式对象,并自动追踪其依赖关系,当依赖的数据发生变化时,会重新计算计算属性的值。

使用案例

ini 复制代码
const count = ref(1);
const plusOne = computed(() => count.value + 1);
console.log(plusOne.value); // 2
count.value++;
console.log(plusOne.value); // 3

除了给 computed​传入一个回调函数以外,你还可以传入一个包含get、set函数​的对象,如下面的例子:

ini 复制代码
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​ 函数接收了一个包含 getter​ 和 setter​ 函数的对象。

​getter​ 函数与之前相同,返回 count.value + 1​。

值得注意的是 setter​ 函数:当我们修改 plusOne.value​ 的值时,就会触发 setter​ 函数。

实际上,setter​ 函数内部会根据传入的参数来修改计算属性所依赖的值 count.value​。

一旦依赖的值发生变化,再次获取计算属性时就会重新执行 getter​ 函数,因此获取到的值也会随之改变。

computed实现原理如下:

computed函数实现

ini 复制代码
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
    setter = NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }

  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)

  return cRef as any
}
  • 首先声明了两个变量getter和setter
  • onlyGetter变量用于判断getterOrOptions是否仅仅包含getter函数。这是通过调用isFunction函数实现的。
  • 如果onlyGetter为真,即getterOrOptions仅包含一个函数,那么将这个函数赋值给getter,setter则被设置为NOOP,即不执行任何操作。
  • 如果onlyGetter为 false,即getterOrOptions包含get和set属性,那么分别将getterOrOptions的get和set属性赋值给getter和setter。

ComputedRefImpl源码实现

kotlin 复制代码
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 _cacheable: boolean

  /**
   * Dev only
   */
  _warnRecursive?: boolean

  constructor(
    private getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean,
    isSSR: boolean,
  ) {
    // 将用户的getter放到effect中,这样能对getter函数进行依赖收集,activeEffect会变为getter生成的effect
    // 传入scheduler调用函数,稍后 依赖的属性变化会调用此方法
    this.effect = new ReactiveEffect(
      () => getter(this._value),
      () =>
 		// 稍后依赖属性变化会执行此调度函数
        triggerRefValue(
          this,
          this.effect._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect
            ? DirtyLevels.MaybeDirty_ComputedSideEffect
            : DirtyLevels.MaybeDirty,
        ),
    )
    this.effect.computed = this
    this.effect.active = this._cacheable = !isSSR
    this[ReactiveFlags.IS_READONLY] = isReadonly
  }

  get value() {
    // 获取原始对象
    const self = toRaw(this)

	// 第一次是DirtyLevels.Dirty,开关开启,说明是脏值,执行函数,然后关闭开关
    if (
      (!self._cacheable || self.effect.dirty) &&
      hasChanged(self._value, (self._value = self.effect.run()!))
    ) {
      triggerRefValue(self, DirtyLevels.Dirty)
    }
	// // 如果当前在effect中访问了计算属性,计算属性可以收集这个effect
    trackRefValue(self)
    if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
      triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
    }
    return self._value
  }

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

  // #region polyfill _dirty for backward compatibility third party code for Vue <= 3.3.x
  get _dirty() {
    return this.effect.dirty
  }

  set _dirty(v) {
    this.effect.dirty = v
  }
  // #endregion
}

在上面的源码中,拆解实现步骤如下:

  • 通过computed函数进入,getter即定义computed时的入参,创建ComputedRefImpl实例的时候也会创建一个ReactiveEffect实例(计算属性effect),后续执行render的过程也会创建响应式的ReactiveEffect实例(响应式effect)
  • 当访问plusOne.value时,触发get value,run()函数指向的是computed实例的getter也是我们定义computed是入参的函数,此函数执行的时候会获取计算属性的结果
  • 执行trackEffect函数(入参的activeEffect是 响应式effect),把 响应式effect放在ref.dep中(ref即computed)(看上一章节同ref的依赖收集)
  • 执行count.value++的时候触发count实例的set value,拿到count.dep(保存的是计算属性effect),执行effect.trigger()(computed.dep中存的是响应式effect)会把响应式函数update放在队列里后面触发更新,update时通过dirty判断是否需要重新获取
相关推荐
明似水18 分钟前
高效管理Dart和Flutter多包项目:Melos工具全解析
android·前端·flutter
城沐小巷22 分钟前
基于SpringBoot+vue高效旅游管理系统
vue.js·spring boot·旅游
helianying5525 分钟前
拥抱开源,助力创新:IBM永久免费云服务器助力开源项目腾飞
运维·服务器·前端·开源
wl851139 分钟前
Vue 入门到实战 八
前端·javascript·vue.js
呦呦鹿鸣Rzh1 小时前
前端工程化-vue项目
前端·javascript·vue.js
大厂在职_fUk1 小时前
Flutter完整开发实战详解(六、 深入Widget原理)
前端·javascript·flutter
liuhaikang1 小时前
【鸿蒙HarmonyOS Next实战开发】实现组件动态创建和卸载-优化性能
java·前端·数据库
m0_748256142 小时前
Spring boot整合quartz方法
java·前端·spring boot
修己xj2 小时前
MediaGo:跨平台视频提取下载的开源神器
前端
m0_528723812 小时前
HTML5 新特性有哪些?
前端·html·html5