Vue 3 中的 watch:响应式数据变化的秘密武器

200 阅读6分钟

Vue.js 是一个流行的前端框架,以其简单易用和强大的双向绑定功能而闻名。在 Vue 3 中,watch 功能得到了进一步增强,成为开发者处理复杂状态逻辑和响应式编程的强大工具。本文将带你体验watch的魅力。

什么是 watch

有的人会问,主播主播,什么是watch? watch 是 Vue 提供的一种监听器,用于监视特定的数据属性或计算属性的变化,并在这些属性发生变化时执行相应的回调函数。它允许你对应用程序的状态做出反应,从而实现动态更新视图、调用 API 或触发其他业务逻辑。

为什么需要 watch

虽然 Vue 的模板语法提供了简洁的方式来表达视图层的逻辑,但在某些情况下,我们需要更细粒度地控制何时以及如何响应数据的变化。比如:

  • 当用户输入表单数据时自动验证;
  • 在路由改变后重新获取数据;
  • 实现复杂的业务规则,如购物车总价计算等。

这些都是 watch 大显身手的地方!

如何使用watch?

watch(source, callback, options)
  • source:可以是单个响应式数据源(如 ref 或者 reactive 对象),也可以是一个返回响应式数据的 getter 函数,甚至可以是一个包含多个数据源的数组。
  • callback:当被监听的数据发生变化时调用的函数。它接收两个参数:新的值 (newVal) 和旧的值 (oldVal)。
  • options(可选):这是一个对象,允许你配置监听器的行为。

1. ref定义的数据

  • 监视ref定义的【基本类型】数据:直接写数据名即可,监视的是其value值的改变。
  • 监视ref定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。
    • 因为 JavaScript 对象是可变的,这意味着可以改变对象内部属性的值而不重新分配整个对象。为了确保能够正确地追踪到对象内部属性的变化,我们需要配置 watch 使用深度监听。
import { ref, watch } from 'vue';
const count = ref(0);
// 监听基本类型的 ref
watch(count, (newVal, oldVal) => {
  console.log(`count changed to ${newVal} from ${oldVal}`);
});
// 改变 count 的值将会触发监听器
count.value++;

const user = ref({
  name: '张三',
  age: 25,
});
// 深度监听对象类型的 ref
watch(user, (newUser, oldUser) => {
  console.log('user 发生了变化');
}, { deep: true });
// 改变 user 内部属性的值将会触发监听器
user.value.age = 26;

注意点

  • 若修改的是ref定义的对象中的属性,newValueoldValue 都是新值,因为它们是同一个对象。
  • 若修改整个ref定义的对象,newValue 是新值, oldValue 是旧值,因为不是同一个对象了。
  • 启用 deep 选项可能会带来一定的性能开销,因为它会导致对整个对象的递归遍历。

2. reactive定义的数据

监视reactive定义的【对象类型】数据,且默认开启了深度监视。

import { reactive, watch } from 'vue';

const state = reactive({
  name: '张三',
  age: 25,
  address: {
    city: '北京',
    street: '朝阳区'
  }
});

// 监听整个 reactive 对象,默认即为深度监听
watch(state, (newState, oldState) => {
  console.log('state 发生了变化');
});

// 改变 state 内部属性的值将会触发监听器
state.age = 26;

3. 侦听一个 getter 函数

使用 watch 来监听由 refreactive 创建的对象的某个特定属性时,不能直接传递对象的属性(例如 user.age

image.png

  • 若该属性值不是【对象类型】,需要写成函数形式。
  • 若该属性值是依然是【对象类型】,可直接编,也可写成函数,建议写成函数。

结论:监视的要是对象里的属性,那么最好写函数式,注意点:若是对象监视的是地址值,需要关注对象内部,需要手动开启深度监视。

  import {reactive,watch} from 'vue'


  let person = reactive({
    name:'张三',
    age:18,
    car:{
      c1:'奔驰',
      c2:'宝马'
    }
  })


  // 监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式
  /* watch(()=> person.name,(newValue,oldValue)=>{
    console.log('person.name变化了',newValue,oldValue)
  }) */

  // 监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
  watch(()=>person.car,(newValue,oldValue)=>{
    console.log('person.car变化了',newValue,oldValue)
  },{deep:true})

4. 监听以上类型的值组成的数组

还有没有东西可以作为侦听器的,有的兄弟,有的,还可以监视上述的多个数据。

import { reactive, ref, watch } from 'vue';

const state = reactive({
  name: '张三',
  age: 25
});
const count = ref(0);

// 组合监听多个属性或数据源
watch([() => state.name, () => state.age, count], ([newName, newAge, newCount], [oldName, oldAge, oldCount]) => {
  console.log(`name or age or count changed. Name: ${newName}, Age: ${newAge}, Count: ${newCount}`);
});

// 改变任意监听的数据源都会触发监听器
state.age = 26;
count.value++;

支持立即执行

watch默认是懒加载的,但有时我们又希望在组件初始化时就执行监听器,为此,Vue 3 的 watch 提供了 immediate配置项。

watch(user, (newUser, oldUser) => {
  console.log('user 发生了变化');
}, { immediate: true});

watchEffect:自动追踪依赖

除了传统的 watch,Vue 3 还引入了一个名为 watchEffect 的新方法。它会立即执行传入的函数,并且自动追踪其中使用的响应式依赖。每当这些依赖发生变化时,watchEffect 将重新运行该函数。

watchEffect(() => {
  console.log(`当前 count 值为: ${count.value}`);
});

实战演练

让我们通过一个简单的例子来看看 watchwatchEffect如何工作。

<template>
  <div class="person">
    <h1>需求:水温达到50℃,或水位达到20cm,则联系服务器</h1>
    <h2 id="demo">水温:{{ temp }}</h2>
    <h2>水位:{{ height }}</h2>
    <button @click="changeTemp">水温+10</button>
    <button @click="changeHeight">水位+1</button>
  </div>
</template>

<script lang="ts" setup name="Person">
import { ref, watch, watchEffect } from 'vue';

// 数据
let temp = ref(0);
let height = ref(0);

// 方法
function changeTemp() {
  temp.value += 10;
}
function changeHeight() {
  height.value += 1;
}

// 使用 watch 实现,明确指出要监视 temp、height
watch([temp, height], ([newTemp, newHeight]) => {
  if (newTemp >= 50 || newHeight >= 20) {
    console.log('联系服务器');
  }
});

// 使用 watchEffect 实现,自动追踪依赖
const stopWatch = watchEffect(() => {
  if (temp.value >= 50 || height.value >= 20) {
    console.log('联系服务器');
  }
  if (temp.value === 100 || height.value === 50) {
    console.log('清理了');
    stopWatch();
  }
});
</script>

在这个例子中:

  • watch 提供了一种明确的方式去监听特定的响应式数据,并根据它们的变化执行相应的逻辑。
  • watchEffect 则提供了一种更加自动化的方式来处理响应式数据的变化,减少了手动管理依赖的需求,并且能够立即执行。

watch vs watchEffect 比较

1. 依赖声明方式

  • watch

    • 显式地指定要监听的数据源。
    • 可以监听单一的响应式数据(如 ref 或 reactive 对象)或多个数据源(通过数组传递)。
    • 适合当你明确知道哪些数据会影响你的业务逻辑时使用。
  • watchEffect

    • 自动追踪回调函数中访问的所有响应式数据。
    • 不需要显式列出依赖项,Vue 会自动识别并在相关数据变化时重新运行回调函数。
    • 更加简洁,适合依赖关系不明显或动态变化的情况。

2. 初始执行时机

  • watch

    • 默认情况下不会在定义时立即执行回调函数。
    • 可以通过设置 { immediate: true } 来让 watch 在初始化时也触发一次回调。
  • watchEffect

    • 总是在定义时立即执行一次回调函数,并且之后每次依赖的数据发生变化时都会重新执行。