【vue3】setup语法糖中计算属性computed和监听watch的使用

在 Vue 3 的 <script setup> 语法糖中计算属性和监听的写法


一、计算属性 (Computed)

使用 computed 函数创建响应式的计算值。

1. 基本用法
<script setup lang="ts">
import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

// 计算属性:全名
const fullName = computed(() => {
  return firstName.value + lastName.value
})

// 修改名字
function changeName() {
  firstName.value = '李'
  lastName.value = '四'
}
</script>

<template>
  <div>
    <p>firstName: {{ firstName }}</p>
    <p>lastName: {{ lastName }}</p>
    <p>fullName: {{ fullName }}</p>
    <button @click="changeName">修改名字</button>
  </div>
</template>
2. 带 setter 的计算属性
<script setup lang="ts">
import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

// 可写的计算属性
const fullName = computed({
  get() {
    return firstName.value + '·' + lastName.value
  },
  set(newValue: string) {
    const [first, last] = newValue.split('·')
    firstName.value = first
    lastName.value = last
  }
})

// 修改计算属性
function updateFullName() {
  fullName.value = '诸葛·亮' // 触发 setter
}
</script>

二、监听 (Watch)

使用 watchwatchEffect 响应数据变化。

1. watch 基础用法
<script setup lang="ts">
import { ref, watch } from 'vue'

const count = ref(0)
const doubleCount = ref(0)

// 监听单个 ref
watch(count, (newValue, oldValue) => {
  console.log(`count变化: ${oldValue}${newValue}`)
  doubleCount.value = newValue * 2
})

// 监听多个源
watch([count, doubleCount], ([newCount, newDouble], [oldCount, oldDouble]) => {
  console.log(`count: ${oldCount}${newCount}, double: ${oldDouble}${newDouble}`)
})

function increment() {
  count.value++
}
</script>
2. watch 高级选项
<script setup lang="ts">
import { ref, watch } from 'vue'

const user = ref({
  name: '张三',
  address: {
    city: '北京'
  }
})

// 深度监听对象
watch(
  user,
  (newVal) => {
    console.log('用户信息变化:', newVal)
  },
  { deep: true, immediate: true } // 立即执行+深度监听
)

// 监听特定属性
watch(
  () => user.value.address.city,
  (newCity, oldCity) => {
    console.log(`城市变化: ${oldCity}${newCity}`)
  }
)

function changeCity() {
  user.value.address.city = '上海'
}
</script>
3. watchEffect 自动依赖追踪
<script setup lang="ts">
import { ref, watchEffect } from 'vue'

const count = ref(0)
const double = ref(0)

// 自动追踪依赖
watchEffect(() => {
  console.log(`count: ${count.value}`)
  double.value = count.value * 2
})

// 带清理副作用的示例
const data = ref(null)
watchEffect(async (onCleanup) => {
  const token = setTimeout(() => {
    data.value = await fetchData(count.value)
  }, 1000)
  
  // 清理函数(取消未完成的请求)
  onCleanup(() => {
    clearTimeout(token)
    console.log('清理上一次的请求')
  })
})
</script>

三、计算属性 vs 监听:使用场景对比

场景推荐方案示例
派生状态计算属性全名 = 姓 + 名
依赖多个数据计算属性总价 = 单价 × 数量
数据变化执行异步操作监听 (watch)搜索建议、表单验证
响应多个数据变化监听 (watch)同时监听多个过滤条件
需要立即执行的副作用watchEffect初始化数据、DOM 操作
需要清理的资源管理watchEffect定时器、事件监听、请求取消

四、最佳实践和常见问题

1. 计算属性最佳实践
<script setup lang="ts">
import { computed, ref } from 'vue'

const price = ref(100)
const quantity = ref(2)

// ✅ 推荐:纯函数,无副作用
const total = computed(() => price.value * quantity.value)

// ❌ 避免:在计算属性中修改状态
const badExample = computed(() => {
  // 不要这样做!
  quantity.value++ // 副作用
  return price.value * quantity.value
})
</script>
2. 监听最佳实践
<script setup lang="ts">
import { watch, ref } from 'vue'

const searchQuery = ref('')
const searchResults = ref([])

// ✅ 推荐:使用 watch 处理异步
watch(searchQuery, async (newQuery) => {
  if (!newQuery.trim()) {
    searchResults.value = []
    return
  }
  
  searchResults.value = await fetchResults(newQuery)
}, { debounce: 300 }) // 添加防抖

// ❌ 避免:在 watchEffect 中直接修改依赖
const count = ref(0)
watchEffect(() => {
  // 可能导致无限循环!
  count.value = count.value + 1
})
</script>
3. 性能优化技巧
<script setup lang="ts">
import { watch, ref } from 'vue'

const largeList = ref([...Array(10000).keys()])
const filter = ref('')

// 优化:避免深度监听大型数组
watch(
  () => [...largeList.value], // 创建副本避免深度监听
  (newList) => {
    console.log('列表变化')
  }
)

// 优化:使用 flush: 'post' 确保 DOM 更新后执行
watch(
  someRef,
  () => {
    // 在 DOM 更新后测量元素
    measureDOMElement()
  },
  { flush: 'post' }
)
</script>

五、完整案例:购物车计算

<script setup lang="ts">
import { ref, computed, watch } from 'vue'

// 商品数据
const products = ref([
  { id: 1, name: '商品A', price: 100, quantity: 2 },
  { id: 2, name: '商品B', price: 200, quantity: 1 },
  { id: 3, name: '商品C', price: 300, quantity: 3 }
])

// 计算属性:总价
const totalPrice = computed(() => {
  return products.value.reduce((sum, product) => {
    return sum + product.price * product.quantity
  }, 0)
})

// 计算属性:折后价(满1000减200)
const discountedPrice = computed(() => {
  return totalPrice.value >= 1000 ? totalPrice.value - 200 : totalPrice.value
})

// 监听:当总价超过2000时提示
watch(totalPrice, (newTotal) => {
  if (newTotal > 2000) {
    console.warn('总价超过2000元')
    alert('您购买的商品总价已超过2000元')
  }
})

// 增加商品数量
function increaseQuantity(id: number) {
  const product = products.value.find(p => p.id === id)
  if (product) product.quantity++
}

// 减少商品数量
function decreaseQuantity(id: number) {
  const product = products.value.find(p => p.id === id)
  if (product && product.quantity > 1) product.quantity--
}
</script>

<template>
  <div class="cart">
    <div v-for="product in products" :key="product.id" class="cart-item">
      <h3>{{ product.name }}</h3>
      <p>单价: {{ product.price }}</p>
      <div class="quantity-control">
        <button @click="decreaseQuantity(product.id)">-</button>
        <span>{{ product.quantity }}</span>
        <button @click="increaseQuantity(product.id)">+</button>
      </div>
      <p>小计: {{ product.price * product.quantity }}</p>
    </div>
    
    <div class="summary">
      <p>总价: {{ totalPrice }}</p>
      <p v-if="totalPrice >= 1000" class="discount">
        优惠价: {{ discountedPrice }} (已减200)
      </p>
    </div>
  </div>
</template>

<style scoped>
.cart-item {
  border: 1px solid #eee;
  padding: 1rem;
  margin-bottom: 1rem;
}
.quantity-control {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.discount {
  color: red;
  font-weight: bold;
}
</style>

六、关键区别总结

特性计算属性 (computed)监听 (watch/watchEffect)
目的派生新数据响应数据变化执行操作
返回值返回一个响应式引用无返回值
依赖追踪自动追踪watch 需显式指定,watchEffect 自动
执行时机惰性计算(使用时求值)立即或异步执行
异步支持不支持支持
使用场景模板中使用的派生数据副作用操作、异步任务
性能高效缓存可能更重
是否可写可通过 setter 实现只读

最佳选择指南:

  1. 使用计算属性

    • 当需要基于现有状态计算新值时
    • 当需要在模板中使用派生数据时
    • 当需要缓存计算结果提高性能时
  2. 使用监听

    • 当需要在状态变化时执行副作用(如API调用)时
    • 当需要响应异步操作时
    • 当需要更细粒度的控制(如防抖、深度监听)时
  3. 使用 watchEffect

    • 当依赖关系复杂且动态变化时
    • 当需要立即执行并自动收集依赖时
    • 当需要清理资源(定时器、订阅)时
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值