Vue3计算属性的缓存机制与Options/Composition API用法你都了解吗

80 阅读15分钟

一、计算属性的基本概念与核心价值

在Vue 3中,计算属性(Computed Property)是依赖响应式数据动态计算得出的衍生值,它的核心价值在于缓存机制——只有当依赖的响应式数据发生变化时,计算属性才会重新计算结果;否则直接复用之前的缓存值。这与方法(Method)的本质区别在于:方法每次调用都会重新执行函数体,而计算属性仅在依赖更新时“按需计算”。

举个生活中的类比:计算属性就像你手机里的“今日步数”——它依赖手机传感器的步数数据(响应式依赖),只有当你走路时(依赖变化),步数才会更新;而不是你每次打开APP都重新计算(像方法那样重复执行)。

二、Options API:计算属性的传统定义方式

Options API是Vue 2的经典语法,在Vue 3中依然兼容。它通过组件选项中的computed字段定义计算属性,语法遵循“选项式”组织逻辑。

1. 基础语法:函数式定义

computed选项中,计算属性以函数形式声明,函数的返回值即为计算结果。函数内通过this访问组件实例的响应式数据(如dataprops等)。

<script>
export default {
  data() {
    return {
      firstName: 'John', // 响应式数据
      lastName: 'Doe'    // 响应式数据
    }
  },
  computed: {
    // 定义计算属性fullName
    fullName() {
      // this指向当前组件实例,可访问data中的属性
      return this.firstName + ' ' + this.lastName
    }
  }
}
</script>

<template>
  <p>全名:{{ fullName }}</p> <!-- 渲染结果:John Doe -->
</template>

关键说明

  • 计算属性会被挂载到组件实例上,模板中可直接通过{{ fullName }}访问。
  • firstNamelastName变化时,fullName会自动重新计算。

2. 进阶:getter与setter

默认情况下,计算属性是只读的(仅包含getter)。若需要让计算属性支持双向绑定(即可以赋值),可通过getset方法定义“读写型”计算属性。

<script>
export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName: {
      // getter:读取fullName时执行
      get() {
        return this.firstName + ' ' + this.lastName
      },
      // setter:给fullName赋值时执行
      set(newValue) {
        // 将新值拆分为 firstName 和 lastName
        const [first, last] = newValue.split(' ')
        this.firstName = first
        this.lastName = last
      }
    }
  }
}
</script>

<template>
  <input v-model="fullName" /> <!-- 输入“Jane Smith”,会触发setter -->
  <p> firstName: {{ firstName }} </p> <!-- 输出:Jane -->
  <p> lastName: {{ lastName }} </p>   <!-- 输出:Smith -->
</template>

关键说明

  • get方法用于计算并返回计算属性的值;
  • set方法接收赋值的新值,可反向更新依赖的响应式数据。

三、Composition API:计算属性的现代定义方式

Composition API是Vue 3的新增语法,通过函数调用的方式组织逻辑,更适合复杂组件的逻辑拆分与复用。定义计算属性需使用computed函数(从vue中导入)。

往期文章归档
免费好用的热门在线工具

1. 基础语法:computed函数

computed函数接收一个计算函数作为参数,返回一个只读的响应式引用(Ref)。计算函数内直接引用响应式变量(需通过.value访问ref类型的数据)。

<script setup>
// 从vue中导入ref(用于定义响应式数据)和computed(用于定义计算属性)
import { ref, computed } from 'vue'

// 定义响应式数据(ref包装)
const firstName = ref('John')
const lastName = ref('Doe')

// 定义计算属性fullName
const fullName = computed(() => {
  // ref类型的数据需通过.value访问其值
  return firstName.value + ' ' + lastName.value
})
</script>

<template>
  <p>全名:{{ fullName }}</p> <!-- 渲染结果:John Doe -->
</template>

关键说明

  • computed返回的fullName是一个Ref对象,模板中使用时会自动解包(无需.value);
  • 计算函数内的依赖(firstName.valuelastName.value)会被Vue自动追踪,依赖变化时计算属性更新。

2. 进阶:带setter的计算属性

与Options API类似,computed函数也支持通过对象参数定义gettersetter,实现“读写型”计算属性。

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

// 定义带setter的计算属性
const fullName = computed({
  // getter:读取时执行
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter:赋值时执行
  set(newValue) {
    const [first, last] = newValue.split(' ')
    firstName.value = first  // 更新响应式数据
    lastName.value = last   // 更新响应式数据
  }
})
</script>

<template>
  <input v-model="fullName" /> <!-- 输入“Jane Smith”,触发setter -->
  <p> firstName: {{ firstName }} </p> <!-- 输出:Jane -->
  <p> lastName: {{ lastName }} </p>   <!-- 输出:Smith -->
</template>

三、Options API vs Composition API:核心差异对比

为了更清晰地理解两种语法的区别,我们通过表格总结核心差异:

维度Options APIComposition API
语法形式组件选项中的computed字段,函数声明调用computed函数,参数为计算逻辑
上下文访问通过this访问组件实例的响应式数据直接引用响应式变量(ref/reactive
逻辑组织按“选项类型”分类(data/computed/methods分开)按“逻辑关注点”组织(相关变量和计算属性放在一起)
灵活性适合简单组件,逻辑分散适合复杂组件,支持逻辑复用(如useTodoFilter

四、实战场景:计算属性的实际运用

计算属性最常用的场景是基于响应式数据生成衍生值,比如过滤列表、格式化数据等。以下以“Todo列表过滤”为例,对比两种API的实现:

1. Options API实现

<script>
export default {
  data() {
    return {
      todos: [ // 待办列表(响应式数据)
        { text: 'Learn Vue', done: true },
        { text: 'Write Blog', done: false },
        { text: 'Build App', done: true }
      ],
      filter: 'all' // 过滤条件(响应式数据:all/done/active)
    }
  },
  computed: {
    // 计算属性:过滤后的待办列表
    filteredTodos() {
      if (this.filter === 'all') return this.todos
      return this.todos.filter(todo => {
        return todo.done === (this.filter === 'done')
      })
    }
  }
}
</script>

2. Composition API实现

<script setup>
import { ref, computed } from 'vue'

// 待办列表(响应式数据)
const todos = ref([
  { text: 'Learn Vue', done: true },
  { text: 'Write Blog', done: false },
  { text: 'Build App', done: true }
])
// 过滤条件(响应式数据)
const filter = ref('all')

// 计算属性:过滤后的待办列表
const filteredTodos = computed(() => {
  if (filter.value === 'all') return todos.value
  return todos.value.filter(todo => {
    return todo.done === (filter.value === 'done')
  })
})
</script>

场景说明
无论是哪种API,计算属性filteredTodos都会自动追踪 todosfilter的变化——当用户切换过滤条件(如从alldone),或待办项的done状态变化时,filteredTodos会自动更新,无需手动监听。

五、课后Quiz:巩固你的理解

问题1:

在Vue 3中,如何用Composition API定义一个带setter的计算属性?要求:

  • 依赖两个ref变量a(初始值1)和b(初始值2);
  • 计算属性sum的getter返回a + b
  • 当给sum赋值时,将值平分给ab(例如sum = 4,则a = 2b = 2)。

答案与解析

<script setup>
import { ref, computed } from 'vue'

const a = ref(1)
const b = ref(2)

const sum = computed({
  get() {
    return a.value + b.value // getter返回a+b
  },
  set(newValue) {
    const average = newValue / 2 // 平分新值
    a.value = average // 更新a
    b.value = average // 更新b
  }
})
</script>

问题2:

请说明计算属性与方法的核心区别,并解释计算属性的“缓存机制”是如何工作的?

答案与解析

  • 核心区别:方法每次调用都会重新执行函数体;计算属性仅在依赖的响应式数据变化时重新计算,否则复用缓存值。
  • 缓存机制:Vue会自动追踪计算属性的响应式依赖(如firstNamelastName),当依赖变化时,标记计算属性为“脏值”,下次访问时重新计算并缓存新值。

六、常见报错与解决方案

1. Options API中计算属性用箭头函数导致this错误

错误示例

computed: {
  fullName: () => this.firstName + this.lastName // this指向undefined
}

原因:箭头函数没有自己的this,会继承外层作用域的this(通常是windowundefined),而非组件实例。
解决方法:使用普通函数定义计算属性(普通函数的this指向组件实例)。
正确示例

computed: {
  fullName() {
    return this.firstName + this.lastName
  }
}

2. Composition API中忘记访问ref.value

错误示例

const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(() => firstName + lastName) // 结果为"[object Object][object Object]"

原因ref是包装对象,其值存储在.value属性中,直接使用ref变量会拿到对象本身。
解决方法:在计算函数中通过.value访问ref的值。
正确示例

const fullName = computed(() => firstName.value + lastName.value)

3. 计算属性依赖非响应式数据

错误示例

const firstName = 'John' // 非响应式
const lastName = 'Doe'
const fullName = computed(() => firstName + lastName) // 不会更新

原因:计算属性的缓存机制仅对响应式数据有效(ref/reactive包装的变量)。
解决方法:将非响应式数据改为refreactive
正确示例

const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(() => firstName.value + lastName.value)

参考链接:vuejs.org/guide/essen…