vue3之computed计算属性
在 Vue 3 中,computed
是一个用于创建计算属性的 API。计算属性是基于其依赖项进行缓存的属性,只有在其依赖项发生变化时才会重新计算。它们通常用于处理复杂的逻辑或数据转换,以便在模板中使用。
使用方法
在 Vue 3 中,可以使用 computed
函数来创建计算属性。
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
参数
computed
函数接受一个函数作为参数,这个函数返回计算属性的值。
计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。
1. 只读计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建
2. 可读写计算属性
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
当你再运行 fullName.value = 'John Doe'
时,setter 会被调用而 firstName 和 lastName 会随之更新。
注意
1. Getter 不应有副作用
计算属性的 getter 应该是纯粹的计算,不应该有任何副作用。副作用是指在计算过程中改变其他状态、进行异步请求或更改 DOM 等操作。计算属性的主要职责是根据其他响应式状态派生出一个新的值。
错误示例:在计算属性的 getter 中进行异步请求或更改状态。
import { ref, computed } from 'vue';
export default {
setup() {
const count = ref(0);
const data = ref(null);
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
data.value = await response.json();
};
// 错误:在计算属性中进行异步请求
const computedData = computed(() => {
fetchData();
return data.value;
});
return {
count,
computedData,
};
},
};
正确示例:使用侦听器(watcher)来处理副作用。
import { ref, computed, watch } from 'vue';
export default {
setup() {
const count = ref(0);
const data = ref(null);
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
data.value = await response.json();
};
// 正确:在侦听器中处理副作用
watch(count, () => {
fetchData();
});
const computedData = computed(() => {
return data.value;
});
return {
count,
computedData,
};
},
};
2. 避免直接修改计算属性值
计算属性的返回值是派生状态,应该被视为只读的。直接修改计算属性的值是没有意义的,应该通过更新其依赖的源状态来触发新的计算。
错误示例:直接修改计算属性的值。
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
// 错误:直接修改计算属性的值
const updateFullName = () => {
fullName.value = 'Jane Smith'; // 这会导致错误
};
return {
firstName,
lastName,
fullName,
updateFullName,
};
},
};
正确示例:通过更新源状态来触发新的计算。
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
// 正确:通过更新源状态来触发新的计算
const updateFullName = () => {
firstName.value = 'Jane';
lastName.value = 'Smith';
};
return {
firstName,
lastName,
fullName,
updateFullName,
};
},
};
计算属性缓存 vs 方法
计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。意味着只要 firstName
和lastName
不改变,无论多少次访问 fullName
都会立即返回先前的计算结果,而不用重复执行 getter 函数。
这也解释了为什么下面的计算属性永远不会更新,因为 Date.now() 并不是一个响应式依赖:
const now = computed(() => Date.now())
相比之下,方法调用总是会在重渲染发生时再次执行函数。
为什么需要缓存呢?
想象一下我们有一个非常耗性能的计算属性 list,需要循环一个巨大的数组并做许多计算逻辑,并且可能也有其他计算属性依赖于 list。没有缓存的话,我们会重复执行非常多次 list 的 getter,然而这实际上没有必要!如果你确定不需要缓存,那么也可以使用方法调用。
1. 计算属性
- 缓存:计算属性是基于其依赖项进行缓存的。只有当依赖项发生变化时,计算属性才会重新计算。
- 声明式:计算属性声明了一个依赖关系,当依赖的响应式数据发生变化时,计算属性会自动更新。
- 性能优化:由于计算属性是缓存的,因此在依赖项不变的情况下,多次访问计算属性不会触发多次计算。
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref('John');
const lastName = ref('Doe');
// 创建计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
return {
firstName,
lastName,
fullName,
};
},
};
在这个示例中,fullName
是一个计算属性,它依赖于 firstName
和 lastName
。当 firstName
或 lastName
发生变化时,fullName
会自动更新,并且在依赖项不变的情况下,多次访问 fullName
不会触发多次计算。
2. 方法
- 不缓存:方法在每次调用时都会重新执行,不会进行缓存。
- 命令式:方法是命令式的,每次调用都会执行相同的逻辑。
- 灵活性:方法可以接受参数,适用于需要动态传递参数的场景。
import { ref } from 'vue';
export default {
setup() {
const firstName = ref('John');
const lastName = ref('Doe');
// 创建方法
const getFullName = () => {
return `${firstName.value} ${lastName.value}`;
};
return {
firstName,
lastName,
getFullName,
};
},
};
在这个示例中,getFullName
是一个方法,每次调用都会重新计算 firstName
和 lastName
的组合。
3. 计算属性 vs 方法
特性 | 计算属性(Computed Properties) | 方法(Methods) |
---|---|---|
缓存 | 是 | 否 |
声明式 | 是 | 否 |
性能优化 | 是 | 否 |
动态参数 | 否 | 是 |
适用场景 | 数据转换、复杂逻辑、依赖多个状态 | 事件处理、动态计算、副作用 |
为 computed() 标注类型
computed()
会自动从其计算函数的返回值上推导出类型:
import { ref, computed } from 'vue'
const count = ref(0)
// 推导得到的类型:ComputedRef<number>
const double = computed(() => count.value * 2)
// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')
你还可以通过泛型参数显式指定类型:
const double = computed<number>(() => {
// 若返回值不是 number 类型则会报错
})
原文地址:https://blog.csdn.net/daoshen1314/article/details/142898721
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!