Vue3 计算属性computed的实现原理

目录
  • computed 的函数签名
  • computed 的实现
  • ComputedRefImpl 类
  • 总结

版本:3.2.31

computed 的函数签名

// packages/reactivity/src/computed.ts

// 只读的
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
)

上面的代码为 computed 的函数重载。在第一个重载中,接受一个 getter 函数,并返回 ComputedRef 类型的值。也就是说,在这种情况下,computed 接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。

如下面的代码所示:

const count = ref(1)
// computed 接受一个 getter 函数
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 错误

在第二个重载中,computed 函数接受一个具有 get 和 set 函数的 options 对象,并返回一个可写的 ref 对象。

如下面的代码所示:

const count = ref(1)
const plusOne = computed({
  // computed 函数接受一个具有 get 和 set 函数的 options 对象
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

第三个重载是第一个重载和第二个重载的结合,此时 computed 函数既可以接受一个 getter 函数,又可以接受一个具有 get 和 set 函数的 options 对象。

computed 的实现

// packages/reactivity/src/computed.ts

export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
  debugOptions?: DebuggerOptions,
  isSSR = false
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>

  // 判断 getterOrOptions 参数 是否是一个函数
  const onlyGetter = isFunction(getterOrOptions)
  if (onlyGetter) {
    // getterOrOptions 是一个函数,则将函数赋值给取值函数getter
    getter = getterOrOptions
    setter = __DEV__
      ? () => {
          console.warn('Write operation failed: computed value is readonly')
        }
      : NOOP
  } else {
    // getterOrOptions 是一个 options 选项对象,分别取 get/set 赋值给取值函数getter和赋值函数setter
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }

  // 实例化一个 computed 实例
  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 参数是 getter 函数还是 options 对象。

如果 getterOrOptions 是 getter 函数,则直接将传入的参数赋值给 computed 的 getter 函数。由于这种情况下的计算属性是只读的,因此不允许设置 setter 函数,并且在 DEV 环境中设置 setter 会报出警告。

如果 getterOrOptions 是 options 对象,则将该对象中的 get 、set 函数分别赋值给 computed 的 gettter 和 setter。

处理完 computed 的 getter 和 setter 后,则根据 getter 和 setter 创建一个 ComputedRefImpl 类的实例,该实例是一个 ref 对象,最后将该 ref 对象返回。

下面我们来看看 ComputedRefImpl 这个类。

ComputedRefImpl 类

// packages/reactivity/src/computed.ts

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

  // value 用来缓存上一次计算的值
  private _value!: T
  public readonly effect: ReactiveEffect<T>

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

  // dirty标志,用来表示是否需要重新计算值,为true 则意味着 脏, 需要计算
  public _dirty = true
  public _cacheable: boolean

  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean,
    isSSR: boolean
  ) {
    this.effect = new ReactiveEffect(getter, () => {
      // getter的时候,不派发通知
      if (!this._dirty) {
        this._dirty = true
        // 当计算属性依赖响应式数据变化时,手动调用 triggerRefValue 函数 触发响应式
        triggerRefValue(this)
      }
    })
    this.effect.computed = this
    this.effect.active = this._cacheable = !isSSR
    this[ReactiveFlags.IS_READONLY] = isReadonly
  }

  get value() {
    // the computed ref may get wrapped by other proxies e.g. readonly() #3376
    // 获取原始对象
    const self = toRaw(this)
    // 当读取 value 时,手动调用 trackRefValue 函数进行追踪
    trackRefValue(self)
    // 只有脏 才计算值,并将得到的值缓存到value中
    if (self._dirty || !self._cacheable) {
      // 将dirty设置为 false, 下一次访问直接使用缓存的 value中的值
      self._dirty = false
      self._value = self.effect.run()!
    }
    // 返回最新的值
    return self._value
  }

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

缓存计算属性,避免多次计算:

为了避免多次访问计算属性时导致副作用函数多次执行,在 ComputedRefImpl 类中定义了一个私有变量 _value 和一个公共变量 _dirty。其中 _value 用来缓存上一次计算的值,_dirty 用来表示是否需要重新计算值,值为 true 时意味着「脏」, 则计算属性需要重新计算。在读取计算属性时,会触发 getter 函数,在 getter 函数中,判断 _dirty 的值是否为 true,如果是,才重新执行副作用,将执行结果缓存到 _value 变量中,并返回最新的值。如果_dirty 的值为 false,说明计算属性不需要重新计算,返回上一次计算的结果即可。

数据变化,计算属性需重新计算:

当计算属性的依赖数据发生变化时,为了使得计算属性是最新的,Vue 在 ComputedRefImpl 类的构造函数中为 getter 创建了一个副作用函数。在该副作用函数中,判断 this._dirty 标记是否为 false,如果是,则将 this._dirty 置为 true,当下一次访问计算属性时,就会重新执行副作用函数计算值。

计算属性中的 effect 嵌套:

当我们在另一个 effect 中读取计算属性的值时,如下面代码所示:

const sumResult = computed(() => obj.foo + obj.bar)

effect(() => {
  // 在该副作用函数中读取 sumResult.value
  console.log(sumResult.value)
})

// 修改 obj.bar 的值
obj.bar++

如上面的代码所示,sumResult 是一个计算属性,并且在另一个 effect 的副作用函数中读取了 sumResult.value 的值。如果此时修改了 obj.bar 的值,期望的结果是副作用函数重新执行,但实际上并未重新触发副作用函数执行。

在一个 effect 中读取计算属性的值,其本质上就是一个典型的 effect 嵌套。一个计算属性内部拥有自己的 effect ,并且它是懒执行的,只有当真正读取计算属性的值时才会执行。当把计算属性用于另外一个 effect 时,就会发生 effect 嵌套,外层的 effect 不会被内层 effect 中的响应式数据收集。因此,当读取计算属性的值时,需要手动调用 trackRefValue 函数进行追踪,当计算属性依赖的响应式数据发生变化时,手动调用 triggerRefValue 函数触发响应。

总结

computed 的实现,它实际上就是一个懒执行的副作用函数,通过 _dirty 标志使得副作用函数可以懒执行。dirty 标志用来表示是否需要重新计算值,当值为 true 时意味着「脏」, 则计算属性需要重新计算,即重新执行副作用。

到此这篇关于Vue3 计算属性computed的实现原理的文章就介绍到这了,更多相关Vue3 computed实现内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Vue3中的 computed,watch,watchEffect的使用方法

    目录 一.computed 二.watch vu2 的写法 Vue3 中这样写 三.watchEffect 一.computed <template> 姓:<input v-model="person.firstName"><br/><br/> 名:<input v-model="person.lastName"><br/><br/> <span>全名:{{person.

  • 浅谈vue3中effect与computed的亲密关系

    在我刚看完vue3响应式的时候,心中就有一个不可磨灭的谜团,让我茶不思饭不想,总想生病.那么这个谜团是什么呢?就是在响应式中一直穿行在tranger跟track之间的effect.如果单纯的响应式原理根本就用不上effect,那么effect到底是干什么的呢? 船到桥头自然直,柳岸花明又一村.苦心人天不负,偶然间我看到了effect测试代码用例! it('should observe basic properties', () => { let dummy const counter = rea

  • Vue3 响应式系统实现 computed

    目录 前言 实现 computed 总结 前言 上篇文章我们实现了基本的响应式系统,这篇文章继续实现 computed. 首先,我们简单回顾一下: 响应式系统的核心就是一个 WeakMap --- Map --- Set 的数据结构. WeakMap 的 key 是原对象,value 是响应式的 Map.这样当对象销毁的时候,对应的 Map 也会销毁. Map 的 key 就是对象的每个属性,value 是依赖这个对象属性的 effect 函数的集合 Set.然后用 Proxy 代理对象的 ge

  • vue3 中 computed 新用法示例小结

    vue3 中 的 computed 的使用,由于 vue3 兼容 vue2 的选项式API,所以可以直接使用 vue2的写法,这篇文章主要介绍 vue3 中 computed 的新用法,对比 vue2 中的写法,让您快速掌握 vue3 中 computed 的新用法. 组合式 API 中使用 computed 时,需要先引入:import { computed } from "vue".引入之后 computed 可以传入的参数有两种:回调函数和 options .具体看看它是如何使用

  • vue3的ref,computed,reactive和toRefs你都了解吗

    目录 1.ref 2.computed 3.reactive 4.toRefs 总结 在vue2中,data函数里返回的值直接为响应式的,但在vue3中我们需要使用一些函数才能达到这个效果. setup函数中拿不到vue的this 1.ref 本质为一个函数,输入参数为一个值(原始类型),返回响应式的对象 2.computed 本质为一个函数,输入参数是一个函数,可以将我们需要的值作为输入函数的返回值 例子:实现点击按钮,屏幕上的数加1 <template> <div id='app'&

  • Vue3 计算属性computed的实现原理

    目录 computed 的函数签名 computed 的实现 ComputedRefImpl 类 总结 版本:3.2.31 computed 的函数签名 // packages/reactivity/src/computed.ts // 只读的 export function computed<T>( getter: ComputedGetter<T>, debugOptions?: DebuggerOptions ): ComputedRef<T> // 可写的 ex

  • Vue中计算属性computed的示例解读

    计算属性 表达式是非常便利的,但是它们实际上只用于简单的运算.在模板中放入太多的逻辑会让模板过重且难以维护,所以引入了计算属性computed,将复杂的逻辑放入计算中进行处理,同时computed有缓存功能,防止复杂计算逻辑多次调用引起的性能问题. computed原理 computed的属性reversedMessage在data中会有一个对我们不可见的cacheReversedMessage属性对应 cacheReversedMessage的值是根据其绑定的data中的message来决定的

  • Vue3 计算属性的用法详解

    目录 computed 计算属性说明 计算属性使用 总结 注意 上一篇博文说了 vue3 项目的 toRefs 函数和 toRef 函数,今天就稍微总结一下 vue3 的计算属性,其实学过 vue2 的宝子们应该都清楚,计算属性这个东西在项目开发过程中使用的还是比较频繁的,使用频率相对来说比较高,所以说咱今天稍微总结一下 vue3 项目中的计算属性,下面开始. computed 计算属性说明 computed 表示计算属性,通常的作用是用来进行数据处理,方便在末班中简化书写. 比如日常在模板中我

  • Vue3计算属性是如何实现的

    目录 计算属性 使用微任务优化调度器 前言: 本篇内容基于Vue3响应式对象是如何实现的(2)实现. 计算属性 Vue3的官方文档中,对于计算属性有这样的描述: 对于任何包含响应式数据的复杂逻辑,我们都应该使用计算属性. 计算属性只会在相关响应式依赖发生改变时重新求值. 从上面的描述可以明确计算属性的需求,计算属性计算的是响应式数据(满足描述1),且计算结果应当被缓存(满足描述2).让我们一个一个来实现,先使用computed创建一个计算属性. function effect(fn) { //

  • Vue.js第三天学习笔记(计算属性computed)

    今天给大家分享下vue.js中的计算属性(computed) 示例一 computed的get属性 html: <template> <div class="input-text"> <input type="text" v-model='firstName'> <input type="text" v-model='lastName'> {{fullName}} </div> <

  • vue计算属性computed的使用方法示例

    本文实例讲述了vue计算属性computed的使用方法.分享给大家供大家参考,具体如下: computed:{ b:function(){ //默认调用get return 值 } } computed:{ b:{ get: set: } } * computed里面可以放置一些业务逻辑代码,一定记得return <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&

  • vue.js计算属性computed用法实例分析

    本文实例讲述了vue.js计算属性computed用法.分享给大家供大家参考,具体如下: 需求:数据msg值为12345,我们现在需要反向显示成54321. 在模板中绑定表达式是非常便利的,但是它们实际上只用于简单的操作.在模板中放入太多的逻辑会让模板过重且难以维护.例如: <!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> &l

  • Vue 计算属性 computed

    目录 1.基础例子 2.计算属性缓存 vs 方法 3.计算属性的 setter 前言: 一般情况下属性都是放到data中的,但是有些属性可能是需要经过一些逻辑计算后才能得出来,那么我们可以把这类属性变成计算属性. 比如以下: <div id="example"> {{ message.split('').reverse().join('') }} </div> 在这个地方,模板不再是简单的声明式逻辑.你必须看一段时间才能意识到,这里是想要显示变量 message

  • vue中计算属性computed理解说明包括vue侦听器,缓存与computed的区别

    一.计算属性(computed) 1.vue computed 说明 当一些数据需要根据其它数据变化时,需要进行处理才能去展示,虽然vue提供了绑定数据表达式绑定的方式,但是设计它的初衷只是用于简单运算的.在模板中放入太多的逻辑会让模板过重且难以维护,对于一些比较复杂和特殊的计算有可能就捉襟见肘了,而且计算的属性写在模板里也不利于项目维护 computed主要的作用: 分离逻辑(模板和数据分离) 缓存值 双向绑定(getter,setter) 2.语法格式 格式 computed:{ [key:

  • 关于vue中计算属性computed的详细讲解

    目录 1.定义 2.用法 3.computed的响应式依赖(缓存) 4.应用场景 附:计算属性的 getter 与 setter 总结 1.定义 computed是vue的计算属性,是根据依赖关系进行缓存的计算,只有在它的相关依赖发生改变时才会进行更新 2.用法 一般情况下,computed默认使用的是getter属性 3.computed的响应式依赖(缓存) 1. computed的每一个计算属性都会被缓存起来,只要计算属性所依赖的属性发生变化,计算属性就会重新执行,视图也会更新.下面代码中,

随机推荐