计算属性
模板内表达式是非常方便的,但它们是为简单操作设计的。太多的逻辑会造成模板臃肿难以维护。例如,有一个嵌套数组:
Vue.createApp({
data() {
return {
author: {
name: '小明',
books: [
'Vue 2 - 高级手册',
'Vue 3 - 基础手册',
'Vue 4 - 神秘之旅'
]
}
}
}
})
在作者是否有books
时想展示不同的内容:
<div id="computed-basics">
<p>他的出版物::</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
</div>
这种情况下模板不再简单可读,你需要仔细阅读几秒钟才能意思到展示内容是依赖于author.books
。如果在模板中想包含这个计算多次会导致更多的问题。
这就是为什么响应式数据里含有复杂逻辑时就需要使用计算属性。
基础示例
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>vue demo4</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<p>他的出版物:</p>
<span>{{ publishedBooksMessage }}
</div>
</body>
<script type="text/javascript">
const app = {
data() {
return {
author: {
name: '小明',
books: [
'Vue 2 - 高级手册',
'Vue 3 - 基础手册',
'Vue 4 - 神秘之旅'
]
}
}
},
computed: {
// 计算属性寄存器
publishedBooksMessage() {
return this.author.books.length>0? 'Yes':'No'
}
}
}
Vue.createApp(app).mount("#app")
</script>
</html>
我们定义了一个计算属性publishedBooksMessage
。试着更改data中的books
数组,publishedBooksMessage
也会相应的发生改变。
你可以在模板中像普通属性一样绑定计算属性,Vue会知道vm.publishedBooksMessage
依赖于vm.author.books
,所以会连动变化。最妙的是我们已声明式的创建了它们之间的依赖关系:计算属性寄存器函数没有副作用,反而更加容易测试和理解。
计算属性缓存 VS 方法
你可能注意到了,我们可以通过方法来实现一样的功能:
<p>{{ calculateBooksMessage() }}
// 组件实例中
methods: {
calculateBooksMessage() {
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
计算属性和方法也确实得到了相同的结果。不同点是计算属性缓存响应式依赖关系。依赖数据源不变化,多次调用计算属性直接返回前面缓存的计算结果。就是说author.books
没有发生变化,调用publishedBooksMessage
会立即返回前面缓存的计算结果而不需要重新运行函数。
下面这个计算属性永远不会更新,因为Date.now()
不是响应式依赖:
computed: {
now() {
return Date.now()
}
}
相对而言,调用方法不管有没有重新渲染都会重复执行。
为什么要缓存计算属性?相像一下,有一个非常耗费资源的list
属性,在计算属性中需要遍历这个大型数组,而且有其他的计算属性依赖于list
,如果不缓存我们就会没有必要的反复耗费资源来读取list
。如果不想缓存就用方法
来代替。
计算属性 Setter
计算属性默认是只读的,但如果需要你也可以进行set:
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>vue demo6</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
</div>
</body>
<script type="text/javascript">
const app = {
data() {
return {
firstName : 'zhou',
lastName : 'yu'
}
},
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
}
const vm = Vue.createApp(app).mount("#app")
// get
console.log(vm.fullName)
// set
vm.fullName = 'John Doe'
console.log(vm.fullName)
</script>
</html>
现在你执行vm.fullName = 'John Doe'
,set方法会更新vm.firstName
和vm.lastName
。
监听器
计算属性适用于大部分案例,是时候定制一个监听器了。Vue提供了很多通用的途径通过监听
观察数据的变化。这在执行异步和耗时操作时监听返回数据变化时非常有用:
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>vue demo7</title>
<script src="https://unpkg.com/vue@next"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
对/错提问:
<input v-model="question" />
<p>{{ answer }}</p>
<p v-if="question_img">
<img :src="question_img" />
</p>
</div>
</body>
<script type="text/javascript">
const app = {
data() {
return {
question: '',
answer: '问题一般以问号结束哦. ;-)',
question_img: '',
count: 0
}
},
watch: {
// 无论问题是否变化,这个函数都会执行
question(newQuestion, oldQuestion) {
console.log("执行" + (++this.count) + "次")
if (newQuestion.indexOf('?') > -1 || newQuestion.indexOf("?")>0) {
this.getAnswer()
}
}
},
methods: {
getAnswer() {
this.answer = '思考中...'
axios
.get('https://yesno.wtf/api')
.then(response => {
this.answer = response.data.answer
this.question_img = response.data.image
})
.catch(error => {
this.answer = '错误! 网络访问失败. ' + error
})
}
}
}
Vue.createApp(app).mount('#app')
</script>
</html>
输入一个问题,以问号结束,执行结果:
上面这个示例,使用watch
选项演示了一个异步操作(网络访问API)。computed
实现不了。
计算属性 VS 监听属性,
Vue提供了一个更加通用的方法实现当前活动实例的数据响应和监听:监听属性,如果有些数据依据其他数据的变化而变化,会导致滥用watch
-- 特别是你有AngularJS背景。但是,很多时候使用计算属性比使用watch
回调更好。看下面的例子:
<div id="demo">{{ fullName }}</div>
const vm = Vue.createApp({
data() {
return {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
}
},
watch: {
firstName(val) {
this.fullName = val + ' ' + this.lastName
},
lastName(val) {
this.fullName = this.firstName + ' ' + val
}
}
}).mount('#demo')
上面这些代码是命令式且重复的,与计算属性比较下:
const vm = Vue.createApp({
data() {
return {
firstName: 'Foo',
lastName: 'Bar'
}
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
}
}).mount('#demo')
是不是好多了?