Vue模板语法 计算属性在模板中的正确使用姿势
1. 计算属性基础
在Vue中,计算属性是一种非常强大的特性。它允许我们基于其他响应式数据进行复杂的计算,并将计算结果缓存起来。这意味着,只有当依赖的响应式数据发生变化时,计算属性才会重新求值。
假设有这样一个场景,我们有一个购物车的应用,需要实时计算购物车中商品的总价。如果不使用计算属性,我们可能会在模板中直接编写复杂的表达式来计算总价。
<div id="app">
<ul>
<li v-for="(item, index) in items" :key="index">
{{ item.name }} - {{ item.price }} 元 x {{ item.quantity }} = {{ item.price * item.quantity }} 元
</li>
</ul>
<p>总价: {{ items.reduce((acc, item) => acc + item.price * item.quantity, 0) }} 元</p>
</div>
<script>
const app = new Vue({
el: '#app',
data() {
return {
items: [
{ name: '苹果', price: 5, quantity: 2 },
{ name: '香蕉', price: 3, quantity: 3 }
]
}
}
});
</script>
虽然上述代码可以计算出总价,但如果在模板中多次需要用到总价,或者计算逻辑变得更加复杂,模板就会变得难以维护。这时,计算属性就派上用场了。
<div id="app">
<ul>
<li v-for="(item, index) in items" :key="index">
{{ item.name }} - {{ item.price }} 元 x {{ item.quantity }} = {{ item.price * item.quantity }} 元
</li>
</ul>
<p>总价: {{ totalPrice }} 元</p>
</div>
<script>
const app = new Vue({
el: '#app',
data() {
return {
items: [
{ name: '苹果', price: 5, quantity: 2 },
{ name: '香蕉', price: 3, quantity: 3 }
]
}
},
computed: {
totalPrice() {
return this.items.reduce((acc, item) => acc + item.price * item.quantity, 0);
}
}
});
</script>
在上述代码中,我们在Vue实例的computed
选项中定义了一个totalPrice
计算属性。在模板中,我们直接使用totalPrice
,而不需要每次都重复计算总价的逻辑。这样不仅使模板更简洁,而且计算属性会被缓存,只有当items
数组发生变化时,totalPrice
才会重新计算。
2. 计算属性的缓存机制
计算属性的缓存机制是其重要特性之一。为了更好地理解这一点,我们来看一个更复杂的例子。
假设我们有一个文章列表,每个文章都有一个发布时间。我们希望在模板中展示距离现在的时间差,例如“1小时前”、“2天前”等。
<div id="app">
<ul>
<li v-for="(article, index) in articles" :key="index">
{{ article.title }} - {{ getTimeDiff(article.publishedAt) }}
</li>
</ul>
</div>
<script>
function getTimeDiff(timestamp) {
const now = new Date();
const diff = now - new Date(timestamp);
const minute = 1000 * 60;
const hour = minute * 60;
const day = hour * 24;
if (diff < minute) {
return Math.floor(diff / 1000) + '秒前';
} else if (diff < hour) {
return Math.floor(diff / minute) + '分钟前';
} else if (diff < day) {
return Math.floor(diff / hour) + '小时前';
} else {
return Math.floor(diff / day) + '天前';
}
}
const app = new Vue({
el: '#app',
data() {
return {
articles: [
{ title: '第一篇文章', publishedAt: new Date('2023-10-01T10:00:00Z') },
{ title: '第二篇文章', publishedAt: new Date('2023-10-02T15:30:00Z') }
]
}
},
methods: {
getTimeDiff(timestamp) {
return getTimeDiff(timestamp);
}
}
});
</script>
在上述代码中,我们通过methods
定义了一个getTimeDiff
方法来计算时间差。在模板中,每次渲染文章列表时,都会调用getTimeDiff
方法。如果文章列表很长,这个计算过程会比较耗时,即使文章的发布时间没有变化,也会重复计算。
接下来,我们使用计算属性来优化这个问题。
<div id="app">
<ul>
<li v-for="(article, index) in articles" :key="index">
{{ article.title }} - {{ article.timeDiff }}
</li>
</ul>
</div>
<script>
function getTimeDiff(timestamp) {
const now = new Date();
const diff = now - new Date(timestamp);
const minute = 1000 * 60;
const hour = minute * 60;
const day = hour * 24;
if (diff < minute) {
return Math.floor(diff / 1000) + '秒前';
} else if (diff < hour) {
return Math.floor(diff / minute) + '分钟前';
} else if (diff < day) {
return Math.floor(diff / hour) + '小时前';
} else {
return Math.floor(diff / day) + '天前';
}
}
const app = new Vue({
el: '#app',
data() {
return {
articles: [
{ title: '第一篇文章', publishedAt: new Date('2023-10-01T10:00:00Z') },
{ title: '第二篇文章', publishedAt: new Date('2023-10-02T15:30:00Z') }
]
}
},
computed: {
articleTimeDiffs() {
return this.articles.map(article => {
return {
...article,
timeDiff: getTimeDiff(article.publishedAt)
};
});
}
},
created() {
this.articles = this.articleTimeDiffs;
}
});
</script>
在上述代码中,我们定义了一个articleTimeDiffs
计算属性。在created
钩子函数中,我们将articles
替换为articleTimeDiffs
计算后的结果。这样,只有当articles
数组发生变化时,articleTimeDiffs
才会重新计算,从而提高了性能。
3. 计算属性的依赖追踪
计算属性能够知道它依赖的响应式数据。当这些依赖数据发生变化时,计算属性会重新求值。
例如,我们有一个学生成绩管理的应用,需要计算学生的平均成绩,并根据平均成绩判断学生是否通过。
<div id="app">
<input type="number" v-model="score1">
<input type="number" v-model="score2">
<input type="number" v-model="score3">
<p>平均成绩: {{ averageScore }}</p>
<p>是否通过: {{ isPassed }}</p>
</div>
<script>
const app = new Vue({
el: '#app',
data() {
return {
score1: 0,
score2: 0,
score3: 0
}
},
computed: {
averageScore() {
return (this.score1 + this.score2 + this.score3) / 3;
},
isPassed() {
return this.averageScore >= 60;
}
}
});
</script>
在上述代码中,averageScore
计算属性依赖于score1
、score2
和score3
。当这三个分数中的任何一个发生变化时,averageScore
会重新计算。而isPassed
计算属性又依赖于averageScore
,所以当averageScore
变化时,isPassed
也会重新计算。
4. 计算属性的Setter和Getter
计算属性默认只有Getter,但我们也可以为计算属性定义Setter。
假设有一个需求,我们有一个全大写的用户名显示,同时也有一个输入框可以修改用户名。当我们在输入框中输入新的用户名时,显示的全大写用户名也会更新。
<div id="app">
<input type="text" v-model="userName">
<p>全大写用户名: {{ upperCaseUserName }}</p>
</div>
<script>
const app = new Vue({
el: '#app',
data() {
return {
_userName: ''
}
},
computed: {
userName: {
get() {
return this._userName;
},
set(newValue) {
this._userName = newValue;
}
},
upperCaseUserName: {
get() {
return this.userName.toUpperCase();
},
set(newValue) {
this.userName = newValue.toLowerCase();
}
}
}
});
</script>
在上述代码中,我们为userName
和upperCaseUserName
都定义了Getter和Setter。当我们修改userName
时,upperCaseUserName
会根据新的userName
值重新计算。而当我们通过v - model
绑定修改upperCaseUserName
时,会调用其Setter,从而更新userName
。
5. 在模板中使用计算属性的注意事项
5.1 避免过度复杂的计算
虽然计算属性可以处理复杂的逻辑,但如果计算逻辑过于复杂,会使代码难以理解和维护。在这种情况下,可以考虑将部分逻辑封装到方法中,然后在计算属性中调用这些方法。
例如,我们有一个复杂的数学计算,涉及多个步骤和公式。
<div id="app">
<p>计算结果: {{ complexCalculation }}</p>
</div>
<script>
function step1(num) {
return num * 2;
}
function step2(num) {
return num + 5;
}
function step3(num) {
return Math.sqrt(num);
}
const app = new Vue({
el: '#app',
data() {
return {
number: 10
}
},
computed: {
complexCalculation() {
let result = step1(this.number);
result = step2(result);
return step3(result);
}
}
});
</script>
通过将复杂的计算步骤封装到方法中,计算属性的逻辑更加清晰。
5.2 注意依赖关系
确保计算属性依赖的响应式数据是正确的。如果依赖关系错误,可能会导致计算属性不会在预期的情况下重新求值。
例如,我们有一个列表过滤的功能,根据输入框的值过滤列表。
<div id="app">
<input type="text" v-model="filterText">
<ul>
<li v-for="(item, index) in filteredItems" :key="index">
{{ item }}
</li>
</ul>
</div>
<script>
const app = new Vue({
el: '#app',
data() {
return {
list: ['苹果', '香蕉', '橙子'],
filterText: ''
}
},
computed: {
filteredItems() {
return this.list.filter(item => item.includes(this.filterText));
}
}
});
</script>
在上述代码中,filteredItems
计算属性依赖于list
和filterText
。如果错误地将filterText
放在了一个非响应式的变量中,或者在修改list
时没有通过Vue的响应式机制,就会导致filteredItems
不能正确更新。
5.3 结合Vue的生命周期钩子
有时候,我们可能需要在计算属性求值之前或之后执行一些操作。这时可以结合Vue的生命周期钩子。
例如,我们有一个计算属性用于获取用户的地理位置信息,并且在获取到地理位置后需要发送一个统计请求。
<div id="app">
<p>地理位置: {{ location }}</p>
</div>
<script>
const app = new Vue({
el: '#app',
data() {
return {
_location: null
}
},
computed: {
location() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(position => {
this._location = {
latitude: position.coords.latitude,
longitude: position.coords.longitude
};
});
}
return this._location;
}
},
created() {
this.$watch('location', newLocation => {
if (newLocation) {
// 发送统计请求
console.log('发送地理位置统计请求', newLocation);
}
});
}
});
</script>
在上述代码中,我们在created
钩子函数中使用$watch
监听location
计算属性的变化。当location
有值时,发送统计请求。
6. 计算属性与方法的对比
6.1 缓存机制
方法在每次调用时都会执行,而计算属性会缓存结果,只有依赖数据变化时才会重新计算。
例如,我们有一个方法和一个计算属性都用于计算一个数的平方。
<div id="app">
<input type="number" v-model="num">
<p>方法计算结果: {{ squareMethod(num) }}</p>
<p>计算属性结果: {{ squareComputed }}</p>
</div>
<script>
const app = new Vue({
el: '#app',
data() {
return {
num: 0
}
},
methods: {
squareMethod(n) {
console.log('方法被调用');
return n * n;
}
},
computed: {
squareComputed() {
console.log('计算属性被计算');
return this.num * this.num;
}
}
});
</script>
在模板中多次使用squareMethod(num)
,每次都会输出“方法被调用”,而squareComputed
只有在num
变化时才会输出“计算属性被计算”。
6.2 应用场景
方法适用于不需要缓存结果,每次调用都需要实时计算的场景,例如触发事件时执行一些临时性的操作。而计算属性适用于基于响应式数据进行缓存计算的场景,例如实时计算购物车总价等。
7. 计算属性在组件中的使用
在Vue组件中,计算属性同样非常有用。
假设我们有一个商品组件,需要根据商品的库存数量显示不同的状态。
<template>
<div>
<p>{{ product.name }}</p>
<p>库存状态: {{ stockStatus }}</p>
</div>
</template>
<script>
export default {
props: {
product: {
type: Object,
required: true
}
},
computed: {
stockStatus() {
if (this.product.stock === 0) {
return '缺货';
} else if (this.product.stock < 10) {
return '库存不足';
} else {
return '有货';
}
}
}
};
</script>
在上述组件中,我们通过props
接收一个product
对象,然后使用计算属性stockStatus
根据商品的库存数量计算库存状态,并在模板中显示。
8. 计算属性与Watch的配合使用
有时候,计算属性和Watch配合使用可以实现更复杂的功能。
例如,我们有一个搜索功能,当搜索框的值变化时,我们不仅要过滤列表,还要记录搜索历史。
<div id="app">
<input type="text" v-model="searchText">
<ul>
<li v-for="(item, index) in filteredList" :key="index">
{{ item }}
</li>
</ul>
<p>搜索历史: {{ searchHistory }}</p>
</div>
<script>
const app = new Vue({
el: '#app',
data() {
return {
list: ['苹果', '香蕉', '橙子'],
searchText: '',
searchHistory: []
}
},
computed: {
filteredList() {
return this.list.filter(item => item.includes(this.searchText));
}
},
watch: {
searchText(newValue) {
if (newValue) {
this.searchHistory.push(newValue);
}
}
}
});
</script>
在上述代码中,计算属性filteredList
根据searchText
过滤列表。而watch
监听searchText
的变化,当searchText
有值时,将其添加到搜索历史中。
9. 深入理解计算属性的原理
Vue的计算属性是基于依赖追踪实现的。当Vue实例创建时,会对计算属性进行初始化。在初始化过程中,会为计算属性创建一个Watcher实例。
这个Watcher实例会收集计算属性依赖的响应式数据的Dep实例。Dep是Vue内部用于依赖收集和派发更新的类。当依赖的响应式数据发生变化时,Dep会通知所有依赖它的Watcher,从而触发计算属性的重新求值。
例如,在前面计算平均成绩的例子中,averageScore
计算属性的Watcher会收集score1
、score2
和score3
的Dep。当score1
发生变化时,score1
的Dep会通知averageScore
的Watcher,averageScore
会重新计算。
10. 优化计算属性的性能
10.1 减少不必要的依赖
尽量确保计算属性只依赖于真正需要的响应式数据。如果计算属性依赖了过多不必要的数据,会导致计算属性在一些不需要重新计算的情况下也进行重新求值。
例如,我们有一个用户信息展示组件,同时有一个全局配置项用于控制是否显示广告。如果用户信息的计算属性依赖了这个广告配置项,即使广告配置项变化不影响用户信息的展示,计算属性也会重新计算。
<template>
<div>
<p>用户名: {{ user.name }}</p>
<p>邮箱: {{ user.email }}</p>
</div>
</template>
<script>
export default {
data() {
return {
user: {
name: '张三',
email: 'zhangsan@example.com'
},
showAd: false
}
},
computed: {
// 错误示例,不应该依赖showAd
userInfo() {
return {
...this.user,
showAd: this.showAd
};
}
}
};
</script>
正确的做法是将不相关的依赖去除,只保留与用户信息相关的依赖。
10.2 合理使用缓存
利用计算属性的缓存机制,避免重复计算。如果计算属性的计算过程比较耗时,并且依赖的数据变化频率不高,那么计算属性的缓存可以显著提高性能。
例如,在处理大数据量的列表过滤时,使用计算属性进行过滤,并结合防抖或节流技术,可以进一步优化性能。
<div id="app">
<input type="text" v-model="filterText">
<ul>
<li v-for="(item, index) in filteredList" :key="index">
{{ item }}
</li>
</ul>
</div>
<script>
import _ from 'lodash';
const app = new Vue({
el: '#app',
data() {
return {
largeList: Array.from({ length: 10000 }, (_, i) => `item${i}`),
filterText: ''
}
},
computed: {
filteredList() {
return _.debounce(() => {
return this.largeList.filter(item => item.includes(this.filterText));
}, 300)();
}
}
});
</script>
在上述代码中,我们使用lodash
的debounce
函数来延迟过滤操作,减少计算次数,结合计算属性的缓存,提高了性能。
通过以上对Vue模板语法中计算属性在模板中的正确使用姿势的详细介绍,相信大家对计算属性有了更深入的理解和掌握,可以在实际项目中更好地运用计算属性来优化代码和提升用户体验。