概述
computed 是vue3中响应式系统的核心API,用于创建基于响应式数据变化的派生值. 什么是派生值?可以简单理解为响应式数据相当于河流的主干, 那么computed的结果就是该主干的支流,是由主干派生出来的. 与普通函数不同的是, computed会将结果缓存起来,只有依赖项状态改变后才会重新计算, 提供高效数据转换.
核心特性:
-
响应式依赖追踪: 自动检测收集响应式依赖
-
惰性计算: 只有在首次执行和依赖项状态变化才会计算
-
结果缓存: 依赖项未变化,返回缓存结果
-
类型安全: 完美支持 TypeScript类型推断
基本用法
创建只读计算属性
import { ref, computed } from 'vue'
const count = ref(0)
// 创建只读计算属性
const doubleCount = computed(() => count.value * 2)
console.log(doubleCount.value) // 0
count.value = 5
console.log(doubleCount.value) // 10 (依赖项变化重新计算)
创建可写计算属性
const firstName = ref('王')
const lastName = ref('五')
// 创建可写计算属性
const fullName = computed({
// getter 函数
get() {
return `${firstName.value}${lastName.value}`
},
// setter 函数
set(newValue) {
// 当设置 fullName 时拆分名字
const [first, last] = newValue.split(' ')
firstName.value = first
lastName.value = last || ''
}
})
// 使用计算属性
console.log(fullName.value) // "王五"
// 修改计算属性
fullName.value = '小 明'
console.log(firstName.value) // "小"
console.log(lastName.value) // "明"
详细介绍
响应原理
-
首次访问:
-
执行计算函数
-
收集函数内访问的响应式依赖
-
缓存计算结果
-
-
依赖变化:
-
标记计算属性为"脏"(d)
-
不立即重新计算
-
-
再次访问:
-
检测到 d 标志
-
重新执行计算函数
-
更新缓存值
-
清除 d 标志
-
参数说明
-
可接收一个getter函数, 返回一个只读ref对象,通过ref.value暴露getter函数的返回值. 或者接收一个带有get和set函数的对象,创建一个可写的ref对象.
-
接收一个配置对象,用于调试
const debugComputed = computed(() => { // 调试逻辑 console.log('计算中...') return someValue.value * 2 }, { // 调试钩子 onTrack(e) { console.log('依赖被追踪:', e) }, onTrigger(e) { console.log('依赖变化触发:', e) } })
参数类型
// 只读
function computed<T>(
getter: (oldValue: T | undefined) => T,
// 查看下方的 "计算属性调试" 链接
debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>
// 可写的
function computed<T>(
options: {
get: (oldValue: T | undefined) => T
set: (value: T) => void
},
debuggerOptions?: DebuggerOptions
): Ref<T>
最佳实践
场景1: 数据格式化
const price = ref(99.9)
const formattedPrice = computed(() => `¥${price.value.toFixed(2)}`)
场景2: 数据转换, 数组排序/筛选
const todos = ref([{ id: 1, text: '学习', done: true }])
const activeTodos = computed(() =>
todos.value.filter(todo => !todo.done)
)
场景3:多状态组合, 基于多个状态计算派生值
const width = ref(100)
const height = ref(50)
const area = computed(() => width.value * height.value)
高级用法
场景: 结合v-model使用
// 父组件
<S v-model="modealData">
// 在子组件中
<template>
<el-input v-model=modelData.value1 />
<el-select v-model=modelData.value2 />
</template>
export defualt {
props: {
modealData: Object
},
setUp(){
const emit = defineEmits('modelData:update')
const computedData = computd(() => {
get(){
const proxyData = new Proxy(props.modealData, key,{
get(target, key){
return Reflect.get(target, key)
},
set(target, key ,value){
emit('modelData:update',{
...target,
[key]: value
})
}
})
return proxyData
},
set(val){
emit('modelData:update',val)
}
})
}
}
性能优化策略
-
避免复杂计算
// 不推荐:计算函数过于复杂 const heavyComputation = computed(() => { return largeArray.value.reduce((acc, item) => { // 非常复杂的计算逻辑... }, 0) }) // 推荐:将复杂计算拆分为多个计算属性 const filtered = computed(() => largeArray.value.filter(/* 条件 */)) const processed = computed(() => filtered.value.map(/* 转换 */))
-
避免副作用
// 错误:计算属性中产生副作用 const invalidComputed = computed(() => { // 禁止:修改外部状态 someState.value = 'changed' // 禁止:发起异步请求 fetchData() })
-
创建可复用计算逻辑
// usePagination.js import { computed } from 'vue' export function usePagination(items, pageSize) { const currentPage = ref(1) const totalPages = computed(() => Math.ceil(items.value.length / pageSize.value) ) const paginatedItems = computed(() => { const start = (currentPage.value - 1) * pageSize.value return items.value.slice(start, start + pageSize.value) }) return { currentPage, totalPages, paginatedItems } } 在组件中使用 import { usePagination } from './usePagination' const items = ref([...]) // 大型数据集 const pageSize = ref(10) const { currentPage, totalPages, paginatedItems } = usePagination( items, pageSize )
总结
计算属性是 Vue 响应式编程的核心范式,正确使用可显著提升应用性能与代码可维护性. 当需要基于现有状态派生新值时,computed
应作为首选方案.在使用计算属性时,理解其核心特性是关键.