模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。

const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})

Computed

如果想根据author是否已有一些书籍来展示不同的信息:

<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>

这里的模板,它的计算依赖于author.books。如果在模板中需要不止一次这样的计算,不想在模板里面重复很多遍的话,推荐使用计算属性来描述依赖响应式状态的复杂逻辑。

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

const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})

const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
<script>

<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>

这里定义了一个计算属性publishedBooksMessagecomputed()方法期望接收一个getter函数,返回值为一个计算属性ref。和其他一般的ref类似,可以通过publishedBooksMessage.value访问计算结果。
计算属性ref也会在模板中自动解包,因此在模板表达式中引用时无需添加.value

Vue的计算属性会自动追踪响应式依赖。它会检测到publishedBooksMessage依赖于author.books,所以当author.books改变时,任何依赖于publishedBooksMessage的绑定都会同时更新。

计算属性和方法区别

如果我们使用函数也可以得到一个类似的结果:

<p>{{ calculateBooksMessage() }}</p>
function calculateBooksMessage() {
return author.books.length > 0 ? 'Yes' : 'No'
}

这和计算属性得到的结果时完全相同的。不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才会重新计算。这说明只要author.books不改变,无论多少次访问publishedBooksMessage都会立即返回之前的计算结果,而不用重复执行getter函数。

像这个计算属性就永远不会更新,因为Date.now()并不是一个响应式依赖。

const now = computed(() => Date.now())

相比之下,方法调用总是会在重渲染发生时再次执行函数。这样的话,如果有一个非常耗性能操作,没有缓存的话会重复执行非常多次getter,但是实际上并不需要。

可写计算属性

计算属性默认是只读的,当尝试修改一个计算属性时,会收到一个运行时警告。只在某些特殊场景中需要用到“可写”的属性,可以同时提供getter和setter来创建:

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

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

const fullName = computed({
//getter
get() {
return firstName.value + ' ' + lastName.value
},
//setter
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>

当给fullName赋值时,fullName.value='Hello World'时,setter会被调用而firstNamelastName会随之更新。

获取上一个值

如果需要,可以通过访问计算属性的getter的第一个参数来获取计算属性返回的上一个值:

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

const count = ref(2)

const alwaysSmall = computed((previous) => {
if (count.value <= 3) {
return count.value
}
return previous
})
</script>

注意点

1. Getter不应该有副作用

计算属性的Getter应只做计算而没有任何其他的副作用,这一点非常重要。不要改变其他状态、在Getter中做异步请求或者更改DOM!

2. 避免直接修改计算属性值

从计算属性返回的值时派生状态,是一个”临时快照“,每当源状态发生变化时,就会创建一个新的快照。更改快照时没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改,应该更新它所依赖的源状态来触发新的计算。