Skip to content

VueJS - 计算属性

模板非常适合简单的表达式,但将过多的复杂逻辑直接放在模板中会使模板变得混乱且难以维护。对于任何涉及响应式(reactive)数据的复杂逻辑,您都应该使用计算属性(computed properties)。

计算属性允许您定义一个属性,该属性的值派生自其他响应式状态。它们基于其响应式依赖项(dependencies)进行缓存(cache),这意味着只有当这些依赖项中的任何一个发生变化时,它们才会重新计算。

假设您有一个包含嵌套响应式数据的对象,并且您想根据它是否有项目来显示一条消息。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Computed Property Example</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<h2>Author: {{ author.name }}</h2>
<!-- Directly in template (can get complex) -->
<!-- 直接放在模板中(可能会变得复杂) -->
<p>Has published books? {{ author.books.length > 0 ? 'Yes' : 'No' }}</p>
<!-- Using computed property (cleaner) -->
<!-- 使用计算属性(更整洁) -->
<p>Has published books (Computed)? {{ publishedBooksMessage }}</p>
<h3>Books:</h3>
<ul>
<li v-for="(book, index) in author.books" :key="index">{{ book }}</li>
</ul>
<button @click="addBook">Add Book</button>
<button @click="removeBook">Remove Book</button>
</div>
<script>
const { createApp, reactive, computed } = Vue;
createApp({
setup() {
const author = reactive({
name: 'Jane Doe',
books: ['Vue 2 - Advanced Guide', 'Vue 3 - Basic Guide']
});
// Define a computed property
// 定义一个计算属性
const publishedBooksMessage = computed(() => {
console.log('Computed property executing...'); // Log to see when it runs
// 打印日志以查看何时执行
// It depends on author.books
// 它依赖于 author.books
return author.books.length > 0 ? 'Yes' : 'No';
});
function addBook() {
author.books.push(`New Book #${author.books.length + 1}`);
}
function removeBook() {
if (author.books.length > 0) {
author.books.pop();
}
}
return {
author,
publishedBooksMessage, // Expose computed property to template
// 将计算属性暴露给模板
addBook,
removeBook
};
}
}).mount('#app');
</script>
</body>
</html>

在这个示例中:

  • 我们有一个响应式的 author 对象。
  • 第一个段落直接在模板中计算消息。这样做是可行的,但如果逻辑更复杂,它就会变得难以阅读。
  • publishedBooksMessage 是一个使用 Vue.computed() 定义的计算属性。它接受一个 getter 函数。
  • 该计算属性依赖于 author.books.length。
  • 在模板中,我们像访问常规属性一样访问 publishedBooksMessage(不需要 ())。
  • 打开您的浏览器控制台(console)。注意 “Computed property executing…” 仅在您添加或删除书籍时(即当 author.books 改变时)打印日志,而不是在组件每次重新渲染时都打印。这展示了其缓存行为。

您可能想知道为什么需要计算属性,而使用方法(method)也能达到同样的效果。

// Using a method instead
// 改用方法
// setup() {
// ...
// function calculateBooksMessage() {
// console.log('Method executing...');
// return author.books.length > 0 ? 'Yes' : 'No';
// }
// return { author, calculateBooksMessage };
// }
// Template: <p>Has published books (Method)? {{ calculateBooksMessage() }}</p>
// 模板: <p>Has published books (Method)? {{ calculateBooksMessage() }}</p>

尽管方法可以产生相同的结果,但关键区别在于缓存(caching)。计算属性基于其响应式依赖项进行缓存。计算属性只有当它的一个依赖项发生变化时才会重新计算。相比之下,每当重新渲染发生时,方法调用总是会运行其函数。

何时使用哪个?

  • 当您需要基于其他响应式状态派生状态时,请使用计算属性,特别是当计算成本较高或您想从缓存中获益时。
  • 当您不需要缓存时,当您需要响应事件(例如在 @click 中)执行操作时,或者当您需要向函数传递参数时,请使用方法。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Computed vs Method Caching</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<p>Computed Value: {{ computedValue }}</p>
<p>Computed Value: {{ computedValue }}</p> <!-- Accessing multiple times -->
<!-- 多次访问 -->
<p>Method Value: {{ methodValue() }}</p>
<p>Method Value: {{ methodValue() }}</p> <!-- Calling multiple times -->
<!-- 多次调用 -->
<button @click="triggerReRender">Trigger Re-render (Change unrelated state)</button>
<!-- 触发重新渲染(改变不相关的状态) -->
<p>Unrelated Value: {{ unrelatedValue }}</p>
</div>
<script>
const { createApp, ref, computed } = Vue;
createApp({
setup() {
const unrelatedValue = ref(0);
const computedValue = computed(() => {
console.log('COMPUTED function running');
return Date.now(); // Example of a value that changes if re-run
// 每次重新运行时都会改变的值的示例
});
function methodValue() {
console.log('METHOD function running');
return Date.now();
}
function triggerReRender() {
unrelatedValue.value++;
}
return {
computedValue,
methodValue,
triggerReRender,
unrelatedValue
};
}
}).mount('#app');
</script>
</body>
</html>

运行此示例并打开控制台:

  1. 初始加载时,‘COMPUTED function running’ 和 ‘METHOD function running’ 都将打印日志(方法可能会因为模板中的多次调用而打印两次)。
  2. 注意计算属性显示的时间戳在两个地方是相同的,而方法的时间戳可能会略有不同。
  3. 点击 ‘Trigger Re-render’ 按钮。这将改变 unrelatedValue,导致重新渲染。
  4. 观察控制台:‘METHOD function running’ 再次打印日志(两次),但 ‘COMPUTED function running’ 不打印日志。计算属性返回其缓存的值,因为其依赖项没有改变。
  5. 计算属性显示的时间戳保持不变,而方法显示的时间戳更新了。

可写的计算属性 (Getters 和 Setters)

Section titled “可写的计算属性 (Getters 和 Setters)”

默认情况下,计算属性是只读的(getter-only)。如果您尝试为计算属性赋新值,您会收到运行时警告。但是,您可以通过提供 get 和 set 函数来创建一个可写的计算属性。

示例:带 Setter 的计算属性 FullName

Section titled “示例:带 Setter 的计算属性 FullName”
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Writable Computed</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<div>First Name: <input v-model="firstName"></div>
<div>Last Name: <input v-model="lastName"></div>
<hr>
<div>Full Name (Computed): <input v-model="fullName"></div>
</div>
<script>
const { createApp, ref, computed } = Vue;
createApp({
setup() {
const firstName = ref('John');
const lastName = ref('Doe');
// Writable computed property
// 可写的计算属性
const fullName = computed({
// Getter: calculates value from source state
// Getter:从源状态计算值
get() {
return firstName.value + ' ' + lastName.value;
},
// Setter: called when fullName is assigned a new value
// Setter:当 fullName 被赋予新值时调用
set(newValue) {
console.log('Setter called with:', newValue);
// Update the source state based on the new value
// 根据新值更新源状态
const names = newValue.split(' ');
firstName.value = names[0] || ''; // Handle potential empty input
// 处理潜在的空输入
lastName.value = names.slice(1).join(' ') || ''; // Handle names with multiple parts
// 处理包含多个部分的名字
}
});
return { firstName, lastName, fullName };
}
}).mount('#app');
</script>
</body>
</html>

在这个示例中:

  • fullName 计算属性同时拥有 get 和 set。
  • get 函数组合了 firstName 和 lastName。
  • 当您在“Full Name”输入字段中输入内容时(因为 v-model 试图设置 fullName),setter 函数会被触发。
  • setter 函数会分割输入的值,并更新原始的 firstName 和 lastName refs。
  • 这创建了一种双向绑定(two-way binding)效果:改变姓或名会更新全名,而改变全名会更新姓和名字段。

计算属性是一个强大的特性,它通过将派生状态逻辑封装在组件的脚本中,使您的模板更加整洁和易于维护。

延伸阅读:Vue 官方文档关于计算属性的内容:https://vuejs.org/guide/essentials/computed.html