一、前言
在本文中,我们将探讨 Vue 中的计算属性(Computed Properties),这是一种强大的数据绑定方式,用于处理响应式数据的复杂逻辑。我们将从介绍计算属性的基本概念开始,然后深入讨论如何定义、使用以及最佳实践。
备注:什么是响应式数据?
Vue 中的数据是响应式的,这意味着当数据发生变化时,相关的视图会自动更新以反映这些变化。计算属性和侦听器等都建立在响应式数据的基础上。
二、内容
2.1 什么是计算属性
计算属性是 Vue 中一种特殊的数据绑定方式,它可以帮助我们以更灵活的方式展示数据,并且可以控制界面的可见性和交互性。
计算属性是依赖于其他数据的属性,当这些依赖数据发生变化时,计算属性会自动更新,无需手动触发。其主要作用是提供多种不同的数据表现方式,同时保持原始数据的不变,以及更便捷地处理界面逻辑。
推荐使用计算属性来处理依赖响应式状态的复杂逻辑,以提高代码可维护性和可读性。
2.2 计算属性的应用场景
计算属性的应用场景包括但不限于:
- 数据过滤和排序:根据一组数据生成过滤或排序后的新数据。
- 格式化数据:将原始数据格式化为可读的字符串。
- 动态类绑定:根据某些条件动态绑定CSS类。
- 条件渲染:根据条件决定是否渲染特定的元素或组件。
2.3 定义计算属性
在Vue中,我们可以通过 computed
选项来定义计算属性。下面是一个示例:
html
<script>
export default {
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
}
}
</script>
<template>
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
</template>
比如上述模板中,我们要根据 author
是否已有一些书籍来展示 'Yes'
或 'No'
。那么,我们可以使用 计算属性 来描述依赖响应式状态的复杂逻辑。
代码重构如下:
html
<script>
export default {
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
},
computed: {
publishedBooksMessage() {
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
}
</script>
<template>
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
</template>
可以看到,我们定义了一个计算属性 publishedBooksMessage
,当我们更改应用 data
中的 books
数组的值后,可以看到 publishedBooksMessage
会随之改变。
在模板中使用计算属性的方式和一般的属性并无二致。Vue 会检测到
this.publishedBooksMessage
依赖于this.author.books
,所以当this.author.books
改变时,任何依赖于this.publishedBooksMessage
的绑定都将同时更新。
2.4 计算属性 vs 方法
你可能会问,计算属性和方法在处理数据时得到的结果是一样的,那它们有什么区别呢?
区别在于计算属性是缓存的。这意味着只有当依赖的数据发生变化时,计算属性才会重新计算。只要依赖属性没有发生变化,多次访问计算属性都只会计算一次,返回之前的计算结果。这对性能优化非常有帮助。
方法(通过methods
选项定义)则不会被缓存,每次调用方法都会执行一次函数,即使结果与之前相同。
因此,如果你有一个需要频繁访问的表达式 ,且结果基于其他响应式数据,使用计算属性会更有效率。
记录:
计算属性值会基于其响应式依赖被缓存 。一个计算属性仅会在其响应式依赖更新时才重新计算。而方法调用总是会在重渲染发生时再次执行函数。
2.5 可写计算属性
默认情况下,计算属性是只读的。如果你需要修改计算属性的值,可以提供一个getter和setter:
html
<template>
<div>
<p>Full Name:</p>
<input v-model="fullName" />
</div>
</template>
<script>
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
}
},
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
[this.firstName, this.lastName] = newValue.split(' ')
}
}
}
}
</script>
现在,当你修改fullName
时,setter会被调用,更新firstName
和lastName
。
注意事项:
- 当提供了 setter 时,必须同时提供 getter。
- 计算属性的 setter 只能接受一个参数,即新的值。
2.6 最佳实践
Getter 不应有副作用
这意味着计算属性的getter函数应该是纯粹的计算,不应该执行会产生副作用的操作,比如异步请求或更改DOM操作。计算属性的主要目的是根据现有的数据计算新的数据,而不是触发其他操作。如果需要在数据变化时执行副作用操作,应该使用侦听器(watcher)来处理。
因此我们总结如下:
- 不要进行异步请求:在计算属性的 getter 中,不应该发起网络请求或执行异步操作,因为这些操作可能需要一些时间来完成,并且会导致计算属性无法立即返回其值。这可能会引发一系列问题,如页面渲染不一致或性能问题。
- 不要直接修改数据:计算属性的 getter 应该只用于计算新的数据,而不应该修改组件的数据或状态。直接在计算属性中修改数据可能会导致不可预测的行为,因为它们可能会触发数据变化的循环或其他副作用。
- 不要执行影响DOM的操作:在计算属性的 getter 中,不应该进行直接的DOM操作,如更改元素的样式或属性。这种操作应该在Vue.js的生命周期钩子或方法中执行。
计算属性应该专注于计算新的派生数据,而不应该改变组件的状态或应用程序的行为。
避免直接修改计算属性值
计算属性的值应该被视为只读,不应该直接修改它们。如果需要更新计算属性的值,应该通过修改它们所依赖的源状态来触发新的计算。这确保了计算属性的值总是基于最新的源数据计算而来。
举个例子。
假设你有一个 Vue 组件,其中有一个计算属性 total
,它计算了两个数据属性 price
和 quantity
的乘积:
js
data() {
return {
price: 10,
quantity: 2
};
},
computed: {
total() {
return this.price * this.quantity;
}
}
在这种情况下,total
是一个计算属性,它的值依赖于 price
和 quantity
的值。你可以在模板中使用 {{ total }}
来显示它,但是不应该像这样尝试直接修改它的值:
js
this.total = 20; // 不应该这样做
为什么不应该这样做呢?
因为 total
是一个计算属性,它的值是由Vue自动计算的,基于它所依赖的数据属性 price
和 quantity
。如果你直接修改 total
的值,Vue将无法跟踪到这个变化,因为它无法知道你已经手动修改了 total
。这可能导致不一致性和意想不到的行为。
相反,如果你想更新 total
的值,应该通过修改它所依赖的源数据属性,即 price
和 quantity
,然后让Vue.js自动重新计算 total
。例如:
js
this.price = 20; // 修改price
this.quantity = 3; // 修改quantity
当你修改 price
或 quantity
时,Vue.js将自动重新计算 total
,并确保 total
的值是正确的。这就是避免直接修改计算属性值的原因,以保持数据的一致性和Vue.js的响应性。计算属性的主要作用是根据源数据计算出一个派生的值,而不是作为普通属性进行手动修改。
2.7 简单案例
假设我们有一个在线商城的商品列表,用户可以根据价格范围、类别等条件筛选商品。我们可以使用计算属性来动态生成筛选后的商品列表,以及计算出满足条件的商品数量。
html
<template>
<div>
<div>
<label for="minPrice">Min Price: </label>
<input id="minPrice" v-model="filters.minPrice" type="number" />
</div>
<div>
<label for="maxPrice">Max Price: </label>
<input id="maxPrice" v-model="filters.maxPrice" type="number" />
</div>
<div>
<label for="category">Category: </label>
<select id="category" v-model="filters.category">
<option value="All">All</option>
<option value="food">food</option>
<option value="Clothing">Clothing</option>
</select>
</div>
<p>Filtered Product Count: {{ filteredProductCount }}</p>
<ul>
<li v-for="product in filteredProducts" :key="product.name">
{{ product.name }} - {{ product.price }} - {{ product.category }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
products: [
{ name: 'Product A', price: 20, category: 'food' },
{ name: 'Product B', price: 30, category: 'Clothing' },
{ name: 'Product C', price: 63, category: 'food' },
{ name: 'Product D', price: 25, category: 'food' },
{ name: 'Product E', price: 56, category: 'Clothing' },
{ name: 'Product F', price: 10, category: 'food' },
{ name: 'Product G', price: 90, category: 'Clothing' },
{ name: 'Product H', price: 22, category: 'food' },
{ name: 'Product I', price: 76, category: 'Clothing' },
{ name: 'Product J', price: 45, category: 'food' },
],
filters: {
minPrice: 0,
maxPrice: 100,
category: 'All',
},
};
},
computed: {
filteredProducts() {
return this.products.filter((product) => {
const priceInRange =
product.price >= this.filters.minPrice &&
product.price <= this.filters.maxPrice;
const categoryMatch =
this.filters.category === 'All' ||
this.filters.category === product.category;
return priceInRange && categoryMatch;
});
},
filteredProductCount() {
return this.filteredProducts.length;
},
},
}
</script>
在上述示例中,我们使用计算属性 filteredProducts
根据用户的筛选条件动态生成商品列表,并使用 filteredProductCount
计算满足条件的商品数量。这样,用户可以根据不同条件快速筛选商品。
三、总结
综上所述,计算属性是 Vue 中强大的特性,它允许你根据响应式数据动态计算属性的值,提高了代码的可维护性和性能。了解计算属性的工作原理并遵循最佳实践,将帮助你更好地利用 Vue 的数据绑定能力。