小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
这里讲的是 Options API 中的计算属性,后面我们还会讲 Composition API 中的计算属性(在 setup 中使用计算属性)。
在讲计算属性之前,我们先来讨论一下 Vue 中关于复杂 data 的处理方式:
-
我们知道,在模板(
template)中可以直接通过插值语法显示一些data中的数据; -
但是在某些情况下,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来显示:
- 比如我们需要对
data中的多个数据进行运算、通过三元运算符来决定结果、数据进行某种转化后显示; - 在模板中使用表达式,可以非常方便地实现,但是这种语法的设计初衷是用于简单的运算;
- 在模板中放入太多的逻辑会让模板过重且难以维护;
- 并且如果多个地方都使用到,那么会有大量重复的代码;
- 比如我们需要对
-
那有没有办法可以将逻辑抽离出去呢?
- 可以,一种方式是将逻辑抽取到一个方法(
method)中,放到methods选项中; - 但这种方式有一个直观的弊端,就是让
data中数据的使用过程变成了方法的调用; - 另一种方式就是使用计算属性(
computed)了;
- 可以,一种方式是将逻辑抽取到一个方法(
那么什么是计算属性呢?
- 官方并没有给出直接的概念解释;
- 而是说:对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性;
- 计算属性将被混入到组件实例中。所有
getter和setter的this上下文自动地绑定为组件实例。
计算属性的用法:
- 选项:
computed - 类型:
{ [key: string]: Function | { get: Function, set: Function } }
下面,我们通过案例来理解一下这个计算属性。
1. 案例演示
我们来看三个案例:
- 我们有两个变量:
firstName和lastName,希望它们拼接之后在界面上显示; - 我们有一个分数:
score- 当
score大于等于60时,在界面上显示“及格”; - 当
score小于60时,在界面上显示“不及格”;
- 当
- 我们有一个变量
message,记录一段文字,比如:"Hello World"- 某些情况下我们是直接显示这段文字;
- 某些情况下我们需要对这段文字进行反转;
实现上述三个案例可以有三种思路:
- 在模板中直接使用表达式;
- 使用
methods对逻辑进行抽取; - 使用计算属性
computed;
1.1 实现思路一:模板语法
<body> 元素中的代码:
<div id="app"></div>
<template id="my-app">
<h2>{{ firstName + ' ' + lastName }}</h2>
<h2>{{ score >= 60 ? '及格' : '不及格' }}</h2>
<h2>{{ message.split(' ').reverse().join(' ') }}</h2>
</template>
<script src="./js/vue.js"></script>
<script>
const App = {
data() {
return {
firstName: 'Shane',
lastName: 'Filan',
score: 80,
message: 'Hello World'
}
},
template: '#my-app'
};
Vue.createApp(App).mount('#app');
</script>
界面显示:
可见,模板语法可以实现预期效果。但是存在以下三个缺点:
- 模板中如果存在大量复杂逻辑,不便于维护(模板中使用表达式的初衷是用于简单的计算);
- 当有多次一样的逻辑时,会存在重复代码;
- 多次使用时,很多运算也需要多次执行,没有缓存;
1.2 实现思路二:methods 实现
<body> 元素中的代码:
<div id="app"></div>
<template id="my-app">
<h2>{{ getFullName() }}</h2>
<h2>{{ getResult() }}</h2>
<h2>{{ getReverseMessage() }}</h2>
</template>
<script src="./js/vue.js"></script>
<script>
const App = {
data() {
return {
firstName: 'Shane',
lastName: 'Filan',
score: 80,
message: 'Hello World'
}
},
methods: {
getFullName() {
return this.firstName + ' ' + this.lastName;
},
getResult() {
return this.score >= 60 ? '及格' : '不及格';
},
getReverseMessage() {
return this.message.split(' ').reverse().join(' ');
}
},
template: '#my-app'
};
Vue.createApp(App).mount('#app');
</script>
通过在模板中调用 methods 选项中的方法,也可以实现预期效果。解决了思路一模板语法中存在大量复杂逻辑,不便于维护的问题以及当有多次一样的逻辑时,会存在重复代码的问题。但也存在以下两个缺点:
- 不够直观(我们想要显示的是一个结果,但现在却变成了方法的调用);
- 多次使用方法时,也没有缓存,方法会被多次调用,结果还是会进行多次重复计算;
1.3 实现思路三:computed 实现
<body> 元素中的代码:
<div id="app"></div>
<template id="my-app">
<h2>{{ fullName }}</h2>
<h2>{{ result }}</h2>
<h2>{{ reverseMessage }}</h2>
</template>
<script src="./js/vue.js"></script>
<script>
const App = {
data() {
return {
firstName: 'Shane',
lastName: 'Filan',
score: 80,
message: 'Hello World'
}
},
computed: {
// 定义了一个叫 fullName 的计算属性
fullName: function() {
return this.firstName + ' ' + this.lastName;
},
// 定义了一个叫 result 的计算属性
result() {
return this.score >= 60 ? '及格' : '不及格';
},
// 定义了一个叫 reverseMessage 的计算属性
reverseMessage() {
return this.message.split(' ').reverse().join(' ');
}
},
template: '#my-app'
};
Vue.createApp(App).mount('#app');
</script>
通过在模板中使用 computed 选项中的计算属性,同样可以实现需求。相较于前面两种实现思路,使用计算属性有下面两个优势:
- 直观上,计算属性更简洁、更优雅;
- 并且,计算属性是有缓存的(一次执行,多次使用),因此性能上有很大提升。
注意:计算属性看起来像是一个函数,但实际上并不是函数,我们在使用它时后面是不加 () 的,这个后面讲 setter 和 getter 时会讲到。