computed 详解
computed() 创建计算属性——一种特殊的响应式数据,它的值由 getter 函数计算得出,当依赖的响应式数据变化时自动更新。
基本用法
import { ref, computed } from 'vitarx'
const count = ref(0)
// 创建计算属性
const double = computed(() => count.value * 2)
console.log(double.value) // 0
count.value = 3
console.log(double.value) // 6计算属性和 ref 一样通过 .value 读取值,在 JSX 模板中会自动解包。
懒计算与脏标记
Vitarx 的 computed 采用懒计算策略:
- 首次访问
.value时才执行 getter 函数 - 依赖变化时只标记脏(dirty),不立即重新计算
- 下次访问
.value时,如果标记为脏才重新计算
import { ref, computed } from 'vitarx'
const count = ref(0)
// getter 不会立即执行
const double = computed(() => {
console.log('getter 执行了')
return count.value * 2
})
// 首次访问 .value 时才执行 getter
console.log(double.value) // 输出: "getter 执行了",然后输出 0
count.value = 5
// 此时 getter 还没有重新执行——只是标记为脏
// 再次访问 .value 时才重新计算
console.log(double.value) // 输出: "getter 执行了",然后输出 10这种机制意味着:即使依赖频繁变化,只要你不读取计算属性的值,就不会产生计算开销。
getter 接收 oldValue 参数
getter 函数接收一个参数——上一次的计算结果(oldValue),第一次计算时为 undefined。你可以利用它做增量计算或条件判断:
import { ref, computed } from 'vitarx'
const items = ref([1, 2, 3])
// 利用 oldValue 做增量计算
const total = computed((oldValue) => {
if (oldValue === undefined) {
// 第一次计算
return items.value.reduce((sum, n) => sum + n, 0)
}
// 后续可以基于 oldValue 做增量更新
return items.value.reduce((sum, n) => sum + n, 0)
})getter/setter 双向计算属性
默认情况下 computed 是只读的。你可以通过传入 { get, set } 对象创建可写的计算属性:
import { ref, computed, createApp } from 'vitarx'
function App() {
const firstName = ref('张')
const lastName = ref('三')
// 可写的计算属性
const fullName = computed({
get: () => `#123;firstName.value}#123;lastName.value}`,
set: (newValue) => {
// 假设姓是第一个字,名是后面的字
firstName.value = newValue[0]
lastName.value = newValue.slice(1)
}
})
return (
<div>
<p>姓名: {fullName}</p>
<button onClick={() => (fullName.value = '李四')}>改名为李四</button>
</div>
)
}
createApp(App).mount('#app')WARNING
如果 computed 没有提供 setter,尝试设置 .value 会在控制台输出警告,修改不会生效。
.dirty 属性
dirty 属性表示计算属性是否需要重新计算——当依赖变化时会标记为脏,但还没有重新计算:
import { ref, computed } from 'vitarx'
const count = ref(0)
const double = computed(() => count.value * 2)
console.log(double.dirty) // true(首次还未计算)
// 访问 .value 触发计算
console.log(double.value) // 0
console.log(double.dirty) // false(刚刚计算过)
// 修改依赖——标记为脏但不重新计算
count.value = 5
console.log(double.dirty) // true(需要重新计算)
// 访问 .value 时才重新计算
console.log(double.value) // 10
console.log(double.dirty) // false.notify() 手动标记脏
notify() 方法用于手动将计算属性标记为脏,使下一次访问 .value 时触发重新计算。通常无需手动调用,除非你需要显式地通知计算属性其值已过时:
import { computed } from 'vitarx'
// 非响应式变量——computed 无法自动追踪它的变化
let multiplier = 2
const double = computed(() => 10 * multiplier)
console.log(double.value) // 20
// 修改非响应式变量——computed 不会感知到变化
multiplier = 3
console.log(double.value) // 仍然是 20
// 手动通知 computed 已脏,下次访问时会重新计算
double.notify()
console.log(double.value) // 30notify() 返回当前实例,支持链式调用:
double.notify().value // 标记脏后立即访问,触发重新计算INFO
notify() 不会立即重新计算,只是标记脏。只有在依赖确实发生了变化但你绕过了响应式系统(如修改了非响应式的外部数据)时,才需要手动调用。
.dispose() 手动销毁
dispose() 方法用于手动销毁计算属性,释放其依赖追踪和副作用资源:
import { ref, computed } from 'vitarx'
const count = ref(0)
const double = computed(() => count.value * 2)
console.log(double.value) // 0
// 手动销毁——不再追踪依赖
double.dispose()
// 之后 count 变化不会影响 double
count.value = 10
console.log(double.value) // 仍然是 0INFO
通常不需要手动调用 dispose()——当计算属性所在的作用域被销毁时,会自动释放。只有在特殊场景下(如动态创建的计算属性需要提前释放)才需要手动调用。
isComputed()
isComputed() 判断一个值是否为 computed() 创建的计算属性实例:
import { ref, computed, isComputed } from 'vitarx'
const count = ref(0)
const double = computed(() => count.value * 2)
isComputed(double) // true
isComputed(count) // false
isComputed(42) // false完整示例
下面是一个综合使用 computed 相关 API 的示例:
import { ref, computed, isComputed, createApp } from 'vitarx'
function App() {
const price = ref(100)
const quantity = ref(2)
const discount = ref(0.9)
// 只读计算属性
const total = computed(() => {
return Math.round(price.value * quantity.value * discount.value)
})
// getter/setter 双向计算属性
const displayPrice = computed({
get: () => `¥#123;price.value}`,
set: (val) => {
// 从字符串中提取数字
const num = parseFloat(val.replace('¥', ''))
if (!isNaN(num)) price.value = num
}
})
// 使用 oldValue 参数
const historyTotal = computed((oldValue) => {
const newVal = price.value * quantity.value * discount.value
return Math.round(newVal)
})
return (
<div style={{ padding: '20px' }}>
<h2>computed 详解示例</h2>
<div style={{ marginTop: '16px' }}>
<h3>只读计算属性</h3>
<p>单价: {price}</p>
<p>数量: {quantity}</p>
<p>折扣: {discount}</p>
<p>总价: {total}</p>
<p>是否为脏: {String(total.dirty)}</p>
<button onClick={() => quantity.value++}>增加数量</button>
</div>
<div style={{ marginTop: '16px' }}>
<h3>可写计算属性</h3>
<p>显示价格: {displayPrice}</p>
<button onClick={() => (displayPrice.value = '¥200')}>设置为 ¥200</button>
</div>
<div style={{ marginTop: '16px' }}>
<p>total 是否为 computed: {String(isComputed(total))}</p>
</div>
</div>
)
}
createApp(App).mount('#app')下一步
- watch 详解 — 学习如何监听响应式数据的变化