模板表达式:
<template>
<span>Name: {{ author.name }}</span>
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
</template>
<script setup>
import { reactive } from 'vue'
const author = reactive({
name: 'John Doe',
books: ['Vue 2 - Advanced Guide', 'Vue 3 - Basic Guide', 'Vue 4 - The Mystery']
})
</script>
<style lang="scss" scoped></style>
在上面的例子中,我们之所以要使用模板表达式,是因为要渲染的数据和模板之间,并非简单的对应关系,需要进行二次处理之后,才能够在模板上使用。
虽然在上面的例子中,使用模板表达式能够解决上面的需求(对数据做二次处理的需求),但是也存在一些问题:
- 因为只能写表达式,不能够写语句,所以这意味着无法支持复杂的运算
- 将计算逻辑写在模板里面,模板显得非常的臃肿
- 相同的计算逻辑要在模板中出现多次,难以维护,计算逻辑理应是能够复用的
因此,正因为有上面的这些问题,所以计算属性登场了。
基本使用
基本使用代码如下:
<template>
<span>Name: {{ author.name }}</span>
<p>Has published books:</p>
<span>{{ isPublishBook }}</span>
</template>
<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 isPublishBook = computed(() => {
// 在计算属性里面,我们就对数据进行二次处理
return author.books.length > 0 ? 'Yes' : 'No'
})
// 计算属性也是响应式,当依赖的数据发生变化,那么计算属性也会重新计算
setTimeout(() => {
author.books = []
}, 2000)
</script>
<style lang="scss" scoped></style>
总结一下,计算属性一般就是对响应式数据进行二次计算,返回一个计算属性的 ref,该 ref 可以在模板中使用。如果所依赖的响应式数据发生了变化,那么该计算属性会重新进行计算。
可写计算属性
一般来讲,计算属性就是对某个响应式数据进行二次计算,之后在模板中读取计算属性的值,这是绝大多数的场景下的使用,因此计算属性默认也就是只读模式。
但是,计算属性是支持可写的模式,需要往 computed 方法中传递一个对象,该对象中有对应的 getter 和 setter:
<template>
<span>name:{{ fullName }}</span>
</template>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Xie')
const lastName = ref('Jie')
const fullName = computed({
// 在读取计算属性值的时候会触发
get() {
return firstName.value + ' ' + lastName.value
},
// 在设置计算属性值的时候会触发
set(newName) {
;[firstName.value, lastName.value] = newName.split(' ')
}
})
// 接下来可能因为某种原因,要设置计算属性
setTimeout(() => {
// 这里就涉及到了设置计算属性
fullName.value = 'Zhang San'
}, 3000)
</script>
<style lang="scss" scoped></style>
最佳实践
- Getter 不应该有副作用
现实生活中,也有使用“副作用”这个词的场景。
现实生活中的副作用指的就是“不期待的效果,但是它发生了”。
程序中的副作用也是类似的意思:
function effect(){
document.body.innerText = 'hello';
}
在上面的例子中,effect 函数内部修改了 document.body 的值,这就直接或者间接影响了其他函数(可能也需要读取 document.body 的值)的执行结果,这个时候我们就称 effect 函数是有副作用。
再比如,一个函数修改了全局变量,也是一个副作用操作:
let val = 1;
function effect(){
val = 2; // 修改全局变量,产生副作用
}
常见的副作用操作还有很多:
- 调用系统 I/O 的 API
- 发送网络请求
- 在函数体内修改外部变量的值
- 使用 console.log 等方法进行输出
- 调用存在副作用的函数
回到 Vue 中的计算属性,一个计算属性的声明中应该描述的是根据一个值派生另外一个值,不应该改变其他的状态,也不应该在 getter 中做诸如异步请求、更改DOM一类的操作。
- 避免直接修改计算属性的值
绝大多数场景下,都应该是读取计算属性的值,而非设置计算属性的值。
从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算。
计算属性和方法
除了计算属性以外,我们还可以定义方法:
<template>
<span>Name: {{ author.name }}</span>
<p>Has published books:</p>
<span>{{ isPublishBook() }}</span>
</template>
<script setup>
import { reactive } from 'vue'
const author = reactive({
name: 'John Doe',
books: ['Vue 2 - Advanced Guide', 'Vue 3 - Basic Guide', 'Vue 4 - The Mystery']
})
function isPublishBook() {
return author.books.length > 0 ? 'Yes' : 'No'
}
</script>
<style lang="scss" scoped></style>
计算属性依赖于响应式数据,然后对响应式数据进行二次计算。只有在响应式数据发生变化的时候,才会重新计算,换句话说,计算属性会缓存所计算的值。
而方法在重新渲染的时候,每次都是重新调用。
<template>
<button v-on:click="a++">A++</button>
<button v-on:click="b++">B++</button>
<p>computedA: {{ computedA }}</p>
<p>computedB: {{ computedB }}</p>
<p>methodA: {{ methodA() }}</p>
<p>methodB: {{ methodB() }}</p>
</template>
<script setup>
import { ref, computed } from 'vue'
const a = ref(1)
const b = ref(1)
// 创建两个计算属性,分别依赖 a 和 b
const computedA = computed(() => {
console.log('计算属性A重新计算了')
return a.value + 1
})
const computedB = computed(() => {
console.log('计算属性B重新计算了')
return b.value + 1
})
function methodA() {
console.log('methodA执行了')
return a.value + 1
}
function methodB() {
console.log('methodB执行了')
return b.value + 1
}
</script>
<style lang="scss" scoped></style>
最佳实践,当需要对数据进行二次计算的时候,就是使用计算属性即可。方法一般是和事件相关联,作为事件的事件处理方法来使用。