Vue3 计算属性 vs 函数方法详解
核心概念理解
计算属性(computed)是什么?
计算属性是基于响应式数据自动计算得出的值,当依赖的数据发生变化时,它会自动重新计算。
函数方法(methods)是什么?
函数方法就是普通的 JavaScript 函数,每次调用时都会执行。
核心区别
1. 缓存机制
<template>
<div>
<!-- 计算属性:有缓存,只计算一次 -->
<p>计算属性: {{ expensiveComputed }}</p>
<p>计算属性: {{ expensiveComputed }}</p>
<p>计算属性: {{ expensiveComputed }}</p>
<!-- 函数方法:无缓存,调用几次执行几次 -->
<p>函数方法: {{ expensiveMethod() }}</p>
<p>函数方法: {{ expensiveMethod() }}</p>
<p>函数方法: {{ expensiveMethod() }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
// 计算属性:有缓存
const expensiveComputed = computed(() => {
console.log('计算属性执行了!') // 只会打印一次
return count.value * 2
})
// 函数方法:无缓存
const expensiveMethod = () => {
console.log('函数方法执行了!') // 会打印三次
return count.value * 2
}
// 测试
setTimeout(() => {
count.value = 10 // 只有当依赖变化时,计算属性才会重新计算
}, 1000)
</script>
2. 使用方式
<template>
<div>
<!-- 计算属性:像变量一样使用(无括号) -->
<p>{{ fullName }}</p>
<p>{{ isAdult }}</p>
<!-- 函数方法:像函数一样调用(有括号) -->
<p>{{ getFullName() }}</p>
<p>{{ checkAdult() }}</p>
<!-- 函数可以传参 -->
<p>{{ greet('Hello') }}</p>
<button @click="handleClick('按钮1')">点击我</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
const age = ref(25)
// 计算属性:基于响应式数据自动计算
const fullName = computed(() => {
return firstName.value + lastName.value
})
const isAdult = computed(() => {
return age.value >= 18
})
// 函数方法:普通函数
const getFullName = () => {
return firstName.value + lastName.value
}
const checkAdult = () => {
return age.value >= 18
}
// 函数可以接收参数
const greet = (greeting) => {
return `${greeting}, ${fullName.value}!`
}
const handleClick = (buttonName) => {
alert(`点击了${buttonName}`)
}
</script>
完整示例对比
<template>
<div>
<h2>购物车示例</h2>
<div>
<h3>商品列表</h3>
<div v-for="item in products" :key="item.id">
<span>{{ item.name }} - ¥{{ item.price }}</span>
<button @click="addToCart(item)">加入购物车</button>
</div>
</div>
<div>
<h3>购物车</h3>
<div v-for="item in cart" :key="item.id">
<span>{{ item.name }} x {{ item.quantity }}</span>
</div>
<!-- 计算属性:购物车总价 -->
<p>总价(计算属性): ¥{{ totalPrice }}</p>
<!-- 函数方法:购物车总价 -->
<p>总价(函数方法): ¥{{ calculateTotalPrice() }}</p>
<!-- 函数方法:带参数的计算 -->
<p>折扣后价格: ¥{{ calculateDiscountedPrice(0.8) }}</p>
</div>
<button @click="updateProductPrice">涨价</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// 商品数据
const products = ref([
{ id: 1, name: '苹果', price: 5 },
{ id: 2, name: '香蕉', price: 3 },
{ id: 3, name: '橙子', price: 4 }
])
// 购物车
const cart = ref([
{ id: 1, name: '苹果', quantity: 2 },
{ id: 2, name: '香蕉', quantity: 3 }
])
// ========== 计算属性 ==========
// 购物车总价 - 自动计算,有缓存
const totalPrice = computed(() => {
console.log('计算属性执行了!')
return cart.value.reduce((total, item) => {
const product = products.value.find(p => p.id === item.id)
return total + (product ? product.price * item.quantity : 0)
}, 0)
})
// ========== 函数方法 ==========
// 计算购物车总价 - 每次调用都执行
const calculateTotalPrice = () => {
console.log('函数方法执行了!')
return cart.value.reduce((total, item) => {
const product = products.value.find(p => p.id === item.id)
return total + (product ? product.price * item.quantity : 0)
}, 0)
}
// 带参数的计算函数
const calculateDiscountedPrice = (discount) => {
return totalPrice.value * discount
}
// 其他函数方法
const addToCart = (product) => {
const existingItem = cart.value.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity++
} else {
cart.value.push({ ...product, quantity: 1 })
}
}
const updateProductPrice = () => {
products.value.forEach(product => {
product.price += 1
})
}
</script>
可写计算属性
计算属性不仅可以读取,还可以设置:
<template>
<div>
<p>全名: {{ fullName }}</p>
<input v-model="fullName" placeholder="输入全名">
<p>姓: {{ firstName }}</p>
<p>名: {{ lastName }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// 可写的计算属性
const fullName = computed({
// getter - 获取值
get() {
return firstName.value + lastName.value
},
// setter - 设置值
set(newValue) {
// 当输入 "李四" 时,分解为姓和名
const names = newValue.split('')
firstName.value = names[0] || ''
lastName.value = names.slice(1).join('') || ''
}
})
</script>
实际应用场景
1. 适合用计算属性的场景
<script setup>
import { ref, computed } from 'vue'
const items = ref([
{ name: '苹果', price: 5, quantity: 2 },
{ name: '香蕉', price: 3, quantity: 3 },
{ name: '橙子', price: 4, quantity: 1 }
])
// ✅ 适合:基于现有数据的派生计算
const totalItems = computed(() => items.value.length)
const totalPrice = computed(() => {
return items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
})
const expensiveItems = computed(() => {
return items.value.filter(item => item.price > 4)
})
const hasExpensiveItems = computed(() => expensiveItems.value.length > 0)
</script>
2. 适合用函数方法的场景
<script setup>
import { ref } from 'vue'
const message = ref('Hello World')
const users = ref([
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 }
])
// ✅ 适合:需要传参的操作
const greet = (name, greeting = 'Hello') => {
return `${greeting}, ${name}!`
}
// ✅ 适合:执行副作用的操作
const saveUser = (user) => {
// 发送请求到服务器
console.log('保存用户:', user)
}
// ✅ 适合:处理事件
const handleClick = (event) => {
console.log('按钮被点击了', event)
}
// ✅ 适合:格式化显示(不依赖响应式数据)
const formatDate = (date) => {
return date.toLocaleDateString()
}
</script>
性能对比示例
<template>
<div>
<h2>性能对比测试</h2>
<p>计数器: {{ count }}</p>
<!-- 计算属性:只计算一次 -->
<div v-for="n in 5" :key="n">
计算属性结果: {{ expensiveComputed }}
</div>
<!-- 函数方法:计算5次 -->
<div v-for="n in 5" :key="n">
函数方法结果: {{ expensiveMethod() }}
</div>
<button @click="increment">增加计数</button>
<button @click="testPerformance">性能测试</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
// 模拟耗时计算
const expensiveOperation = (num) => {
let result = 0
for (let i = 0; i < 1000000; i++) {
result += num
}
return result
}
// 计算属性:有缓存
const expensiveComputed = computed(() => {
console.log('计算属性执行')
return expensiveOperation(count.value)
})
// 函数方法:无缓存
const expensiveMethod = () => {
console.log('函数方法执行')
return expensiveOperation(count.value)
}
const increment = () => {
count.value++
}
const testPerformance = () => {
console.time('计算属性')
for (let i = 0; i < 1000; i++) {
expensiveComputed.value
}
console.timeEnd('计算属性')
console.time('函数方法')
for (let i = 0; i < 1000; i++) {
expensiveMethod()
}
console.timeEnd('函数方法')
}
</script>
总结
| 特性 | 计算属性 | 函数方法 |
|---|---|---|
| 缓存 | ✅ 有缓存 | ❌ 无缓存 |
| 执行时机 | 依赖变化时执行 | 每次调用时执行 |
| 传参 | ❌ 不能传参 | ✅ 可以传参 |
| 副作用 | ❌ 不应该有副作用 | ✅ 可以有副作用 |
| 使用方式 | 像变量一样 {{ fullName }} | 像函数一样 {{ getFullName() }} |
| 适用场景 | 派生计算、数据转换 | 事件处理、异步操作、复杂逻辑 |
简单记忆法则:
- 需要缓存、基于响应式数据计算 → 用
computed - 需要传参、执行副作用、处理事件 → 用
methods