计算属性
模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。
-
根据作者今年是否看过书展示不同信息
<script lang="ts" setup>
import { ref, reactive } from "vue"
const author = reactive({
name: 'John Doe',
books: [
'老人与海',
'百年孤独',
'丧钟为谁而鸣'
]
})
</script>
<template>
<div class="container">
<p>2024是否有看过书籍:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
</div>
</template>
<style lang="scss" scoped>
.container {}
</style>
效果:
-
这样判断使模版逻辑看起来比较复杂
因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑
<script lang="ts" setup>
import { ref, reactive, computed } from "vue"
const author = reactive({
name: 'John Doe',
books: [
'老人与海',
'百年孤独',
'丧钟为谁而鸣'
]
})
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<div class="container">
<p>2024是否有看过书籍:</p>
<span>{{ publishedBooksMessage }}</span>
</div>
</template>
<style lang="scss" scoped>
.container {}
</style>
-
我们在这里定义了一个计算属性 publishedBooksMessage。computed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 publishedBooksMessage.value 访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value。
-
Vue 的计算属性会自动追踪响应式依赖。它会检测到 publishedBooksMessage 依赖于 author.books,所以当 author.books 改变时,任何依赖于 publishedBooksMessage 的绑定都会同时更新。
计算属性缓存 vs 方法
-
使用方法实现上述功能
...
...
// 组件中
function calculateBooksMessage() {
return author.books.length > 0 ? 'Yes' : 'No'
}
...
...
<p>{{ calculateBooksMessage() }}</p>
...
...
区别:
-
计算属性值会基于其响应式依赖被缓存,一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books 不改变,无论多少次访问 publishedBooksMessage 都会立即返回先前的计算结果,而不用重复执行 getter 函数。
-
方法调用总是会在重渲染发生时再次执行函数
为什么需要缓存呢?想象一下我们有一个非常耗性能的计算属性 list,需要循环一个巨大的数组并做许多计算逻辑,并且可能也有其他计算属性依赖于 list。没有缓存的话,我们会重复执行非常多次 list 的 getter,然而这实际上没有必要!如果你确定不需要缓存,那么也可以使用方法调用。
可写计算属性
-
计算属性默认是只读的。
-
当你尝试修改一个计算属性时,你会收到一个运行时警告。
-
只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建:
<script setup lang="ts">
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
})
// 赋值后会执行 计算属性的 setter 方法 从而改变 firstName 和 lastName 的值
const changeName = () => {
// 不推荐这么做 避免直接修改计算属性值
fullName.value = '张 四'
// 推荐 应该更新它所依赖的源状态以触发新的计算
// lastName.value = '四'
}
</script>
<template>
<div class="container">
<button class="button" @click="changeName">点击改名名字</button>
<p>{{ fullName }}</p>
</div>
</template>
<style lang="scss" scoped>
.container {}
</style>
-
现在当你再运行 fullName.value = '张 四' 时,setter 会被调用而 firstName 和 lastName 会随之更新。
注意:这里是做演示并不推荐计算属性修改,除非特殊需要
解构赋值语法是一种 Javascript 表达式。可以将数组中的值或对象的属性取出,赋值给其他变量。
let a, b, rest;
[a, b] = [10, 20];
console.log(a);
// 输出: 10
console.log(b);
// 输出: 20
//... 扩展运算符
// 将剩余的值 赋值给rest变量 就这个意思
// 函数上使用就是多参数 比如 function f1(...args)
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(rest);
// 输出: Array [30, 40, 50]
最佳实践
-
Getter 不应有副作用
-
计算属性的 getter 应只做计算而没有任何其他的副作用,这一点非常重要,请务必牢记。
-
举例来说,不要在 getter 中做异步请求或者更改 DOM!一个计算属性的声明中描述的是如何根据其他值派生一个值。因此 getter 的职责应该仅为计算和返回该值。在之后的指引中我们会讨论如何使用侦听器根据其他响应式状态的变更来创建副作用。
-
避免直接修改计算属性值
-
从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。
-
更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算。