Vue3 中watch和computed

190 阅读3分钟

Vue 3 中 computedwatch 深度解析

在 Vue 3 组合中,响应式工具的类型安全使用至关重要。以下是详细说明

一、watch 侦听器

1. 基础类型监听

<template>
  <div>实际参数1={{count}}</div>
  <div>
    <button @click="count++">点击</button>
  </div>
</template>

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

const count = ref<number>(0)
const state = reactive({items: [] as string[]});


watch(count, (newVal:number, oldVal:number) => {
  state.items.push(String(count.value))
  console.log('newVal, oldVal === ', newVal, oldVal)
})

watch(() => state.items, () => {
  console.log('state.items ===', state.items)
},{deep: true})

</script>

2. 多源监听

<template>
  <div>实际参数1={{count}}</div>
  <div>
    <button @click="count++">点击</button>
  </div>
</template>

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

const count = ref<number>(0)
const state = reactive({items: [] as string[]});


watch(count, (newVal:number, oldVal:number) => {
  state.items.push(String(count.value))
  console.log('newVal, oldVal === ', newVal, oldVal)
})

watch(() => state.items, () => {
  console.log('state.items ===', state.items)
},{deep: true})

watch([count, state], ([newCount, newState]:[number, object], [oldCount, oldState]:[number, object])=> {
  console.log('[newCount, newState], [oldCount, oldState] =', newCount, newState, oldCount, oldState)
})
</script>

3. 深度监听对象

<template>
  <div>实际参数1={{product}}</div>
  <div>
    <button @click="product.price++">点击</button>
  </div>
</template>

<script setup lang="ts">
import {
  reactive,
  watch
} from "vue";
interface Product {
  id: number,
  price: number,
  name: string,
  specs: {
    color: string,
    weight: number
  }
}

const product = reactive<Product>({
  id: 1,
  price: 131,
  name: 'Bwm',
  specs: {
    color: 'red',
    weight: 80
  }
})

watch(() => product.price, // 创建新引用触发深度监听
  (newProduct,oldProduct) => {
    console.log('newVal,oldVal === ', newProduct,oldProduct)
  },{deep: true})
</script>


二、watchEffect 高级用法

watchEffect() 允许我们自动跟踪回调的响应式依赖,且会立即执行。

1. 基本用法

<template>
  <div>实际参数1={{product}}</div>
  <div>
    <button @click="product.price++">点击</button>
  </div>
</template>

<script setup lang="ts">
import {
  reactive,
  ref,
  watch, watchEffect
} from "vue";
interface Product {
  id: number,
  price: number,
  name: string,
  specs: {
    color: string,
    weight: number
  }
}

const product = reactive<Product>({
  id: 1,
  price: 131,
  name: 'Bwm',
  specs: {
    color: 'red',
    weight: 80
  }
})
const totalPrice = ref<number>(0)

watchEffect(()=> {
  totalPrice.value = product.price + 2
  console.log('totalPrice === ', totalPrice)
})
</script>

2. 清理副作用

<template>
  <div>实际参数1={{product}}</div>
  <div>
    <button @click="product.price+=10">点击</button>
  </div>
</template>

<script setup lang="ts">
import {
  reactive,
  ref,
  watchEffect,
  onWatcherCleanup
} from "vue";

interface Product {
  id: number,
  price: number,
  name: string,
  specs: {
    color: string,
    weight: number
  }
}

const product = reactive<Product>({
  id: 1,
  price: 131,
  name: 'Bwm',
  specs: {
    color: 'red',
    weight: 80
  }
})
const totalPrice = ref<number>(0)

watchEffect(()=> {
  totalPrice.value = product.price + 2
  console.log('totalPrice === ', totalPrice)
  onWatcherCleanup(() => {
    console.log('onWatcherCleanup ===')
  })
})
</script>

三、computed 计算属性

1. 对象类型计算

<template>
  <div>实际参数1={{totalPrice}}</div>
  <div>
    <button @click="product.price+=10">点击</button>
  </div>
</template>

<script setup lang="ts">
import {
  reactive,
  ref,
  computed
} from "vue";
interface Product {
  id: number,
  price: number,
  name: string,
  specs: {
    color: string,
    weight: number
  }
}

const product = reactive<Product>({
  id: 1,
  price: 131,
  name: 'Bwm',
  specs: {
    color: 'red',
    weight: 80
  }
})
const totalPrice = computed<number>(()=>product.price += 10)
</script>

2. 可写计算属性

<template>
  <div>{{fullName}}</div>
  <div>姓名: <el-input v-model="fullName"/></div>
</template>

<script setup lang="ts">
import {
  reactive,
  ref,
  computed
} from "vue";
const firstName = ref<string>('Jane')
const lastName = ref<string>('Smith')

const fullName = computed<string>({
  get() {
    return `${firstName.value} ${lastName.value}`
  },
  set(fullName:string) {
    const [newFirstName, newLastName] = fullName.split(' ')
    firstName.value = newFirstName
    lastName.value = newLastName
  }
})
</script>

四、computed vs watch vs watchEffect 对比

特性computedwatchwatchEffect
目的派生值响应变化执行操作自动追踪依赖执行操作
返回值ref对象停止函数停止函数
初始化执行立即计算可配置(immediate)立即执行
依赖声明自动显式指定自动
缓存
异步支持
清理机制
调试钩子
适合场景数据转换/格式化精确控制的操作自动追踪依赖的副作用

总结

  1. computed

    • 用于派生状态
    • 具有缓存机制
    • 适合数据转换和格式化
    • 模板中优先使用
  2. watch

    • 用于执行副作用
    • 提供精确控制
    • 适合异步操作、DOM操作
    • 需要显式声明依赖
  3. watchEffect

    • 自动追踪依赖
    • 立即执行
    • 适合多个依赖的简单副作用
    • 提供清理机制

黄金法则

  • 需要派生值 → 用 computed
  • 需要响应变化执行操作 → 用 watchwatchEffect
  • 需要精确控制依赖 → 用 watch
  • 需要自动追踪多个依赖 → 用 watchEffect
  • 避免computed 中产生副作用
  • 总是在副作用中清理资源

通过合理选择和使用这些 API,可以构建出高效、可维护的 Vue 3 应用程序。