计算属性

计算属性是一种特殊的响应式数据——它的值由其他响应式数据计算得出,当依赖变化时自动更新。本页介绍 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,不打印

可写计算属性

默认情况下计算属性是只读的。如果你需要在赋值时执行自定义逻辑,可以传入带有 getset 的对象:

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')

点击按钮后,fullNameset 被触发,将 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>

下一步

  • 监听器 — 学习如何监听数据变化并执行副作用