计算属性
计算属性是一种特殊的响应式数据——它的值由其他响应式数据计算得出,当依赖变化时自动更新。本页介绍 computed 的用法。
基本用法
computed 接收一个 getter 函数,返回一个只读的计算属性:
tsx
import { ref, computed, createApp } from 'vitarx'
function App() {
const count = ref(0)
const double = computed(() => count.value * 2)
return (
<div>
<p>计数: {count}</p>
<p>双倍: {double}</p>
<button onClick={() => count.value++}>+1</button>
</div>
)
}
createApp(App).mount('#app')当 count 变化时,double 会自动重新计算。
访问计算结果
通过 .value 访问计算结果(在模板中会自动解包):
tsx
const count = ref(3)
const double = computed(() => count.value * 2)
console.log(double.value) // 6缓存与懒计算
计算属性有两个重要特性:
懒计算
计算属性不会在创建时立即计算,而是在首次访问 .value 时才执行 getter。之后只有当依赖变化时,才会标记为"脏"(需要重新计算),并在下次访问时重新计算。
tsx
const count = ref(0)
const double = computed(() => {
console.log('计算执行了') // 只在访问 .value 且依赖变化时执行
return count.value * 2
})
// 此时 getter 还没有执行
console.log(double.value) // 输出: "计算执行了",然后输出 0
console.log(double.value) // 直接输出 0,不会再次执行 getter
count.value = 5
// 此时 getter 还没有重新执行
console.log(double.value) // 输出: "计算执行了",然后输出 10
console.log(double.value) // 直接输出 10,不会再次执行 getter缓存结果
如果依赖没有变化,多次访问 .value 会返回缓存的结果,不会重复计算。这和普通函数调用不同——普通函数每次调用都会执行。
tsx
const items = ref([1, 2, 3])
const total = computed(() => {
console.log('计算总数')
return items.value.reduce((sum, n) => sum + n, 0)
})
// 依赖不变,多次访问只计算一次
total.value // "计算总数",返回 6
total.value // 直接返回 6,不打印
total.value // 直接返回 6,不打印可写计算属性
默认情况下计算属性是只读的。如果你需要在赋值时执行自定义逻辑,可以传入带有 get 和 set 的对象:
tsx
import { ref, computed, createApp } from 'vitarx'
function App() {
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed({
get: () => firstName.value + lastName.value,
set: (val: string) => {
// 假设姓是第一个字,名是剩余部分
firstName.value = val.charAt(0)
lastName.value = val.slice(1)
}
})
return (
<div>
<p>姓名: {fullName}</p>
<button
onClick={() => {
fullName.value = '李四'
}}
>
改名为李四
</button>
</div>
)
}
createApp(App).mount('#app')点击按钮后,fullName 的 set 被触发,将 firstName 改为 '李',lastName 改为 '四',界面自动更新。
完整示例
下面是一个购物车价格计算的完整示例:
tsx
import { ref, computed, createApp } from 'vitarx'
function App() {
const price = ref(100)
const quantity = ref(1)
const discount = ref(0.9) // 9 折
// 只读计算属性:小计
const subtotal = computed(() => price.value * quantity.value)
// 只读计算属性:折后价
const total = computed(() => Math.round(subtotal.value * discount.value))
// 可写计算属性:折扣百分比显示
const discountPercent = computed({
get: () => Math.round(discount.value * 100),
set: (val: number) => {
if (val >= 0 && val <= 100) {
discount.value = val / 100
}
}
})
return (
<div style={{ padding: '20px', maxWidth: '400px', margin: '0 auto' }}>
<h2>购物车</h2>
<div style={{ margin: '12px 0' }}>
<label>单价: </label>
<input
type="number"
value={price}
onInput={(e) => {
price.value = Number((e.target as HTMLInputElement).value)
}}
/>
</div>
<div style={{ margin: '12px 0' }}>
<label>数量: </label>
<button onClick={() => quantity.value--}>-</button>
<span style={{ margin: '0 8px' }}>{quantity}</span>
<button onClick={() => quantity.value++}>+</button>
</div>
<div style={{ margin: '12px 0' }}>
<label>折扣: {discountPercent}%</label>
<input
type="range"
min="0"
max="100"
value={discountPercent}
onInput={(e) => {
discountPercent.value = Number((e.target as HTMLInputElement).value)
}}
style={{ width: '100%' }}
/>
</div>
<hr />
<p>小计: ¥{subtotal}</p>
<p style={{ fontSize: '20px', fontWeight: 'bold' }}>折后价: ¥{total}</p>
</div>
)
}
createApp(App).mount('#app')API 参考
typescript
// 只读计算属性
declare function computed<T>(getter: (oldValue: T | undefined) => T): Computed<T>
// 可写计算属性
declare function computed<T>(options: {
get: (oldValue: T | undefined) => T
set: (newValue: T) => void
}): Computed<T>下一步
- 监听器 — 学习如何监听数据变化并执行副作用