Skip to content

侦听器

基本使用

watch 侦听器的第一个参数可以是 ref对象(包括计算属性)、一个响应式对象、一个 getter 函数或多个数据源组成的数组。

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

  const x = ref(1);

  // 一个ref对象
  watch(x, (newVal, oldVal) => {
    console.log(`x is ${newVal}`);
  });
  
  setInterval(() => {
    x.value++;
  }, 1000);
</script>
vue
<script setup lang="ts">
  import { reactive, watch } from "vue";

  const obj = reactive({
    count: 1
  });

  // vue3中watch默认是深层次侦听,所以侦听对象是obj,但obj.count变化时,也会侦听到
  watch(obj, (newVal, oldVal) => {
    console.log(`new value is ${newVal.count}`);
  });

  setInterval(() => {
    obj.count++;
  }, 1000);
</script>
vue
<script setup lang="ts">
  import { ref, watch } from "vue";

  const x = ref(1);
  const y = ref(1);

  // 一个 getter 函数
  watch(() => x.value + y.value, (newVal, oldVal) => {
      console.log(`total is ${newVal}`);
    }
  );
  
  setInterval(() => {
    x.value++;
    y.value *= 2;
  }, 1000);
</script>
vue
<script setup lang="ts">
  import { ref, watch } from "vue";

  const x = ref(1);
  const y = ref(1);

  // 多个数据源组成的数组
  watch([x, y], ([newXValue, newYValue]) => {
    console.log(`x is ${newXValue}`);
    console.log(`y is ${newYValue}`);
  });

  setInterval(() => {
    x.value++;
    y.value *= 2;
  }, 1000);
</script>

提示

使用 watch 时,不能直接侦听响应式对象内部的某个值,这时候需要通过一个 getter 函数来解决

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

  const obj = reactive({
    count: 1
  });

  // 错误:第一个参数是 number,不在 watch 第一个参数类型中
  watch(obj.count, (newVal, oldVal) => {
    console.log(`new value is ${newVal}`);
  });

  watch(() => obj.count, (newVal, oldVal) => {
    console.log(`new value is ${newVal}`);
  });

  setInterval(() => {
    obj.count++;
  }, 1000);
</script>

一次性侦听器3.4+

每当被侦听源发生变化时,侦听器的回调就会执行。如果希望回调只在源变化时触发一次,请使用 once: true 选项。

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

const obj = reactive({
  count: 1
});

watch(
  () => obj.count,
  (newVal, oldVal) => {
    console.log(`new value is ${newVal}`);
  },
  { once: true }
);

setInterval(() => {
  obj.count++;
}, 1000);
</script>

watchEffect

当页面初次渲染时,如果想触发一次侦听器,可以使用 immediate: true 选项,但也可以使用 watchEffect 函数。

vue
<template>
	<button @click="todoId++">change</button>
</template>

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

  const todoId = ref(1);

  watch(
    todoId,
    async () => {
      const response = await fetch(
        `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
      );
      const data = await response.json();
      console.log(data);
    },
    { immediate: true }
  );
</script>

使用 watchEffect 函数重写:

提示

使用 watchEffect函数,不再需要明确指定侦听源,函数内部会自动追踪被用到的响应式数据。

vue
<template>
	<button @click="todoId++">change</button>
</template>

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

  const todoId = ref(1);

  watchEffect(async () => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
    );
    const data = await response.json();
    console.log(data);
  });
</script>

watch 与 watchEffect 的区别?

watchwatchEffect
数据源只追踪明确指定的数据源自动追踪使用到的数据源
触发回调默认只在数据源发送变化时触发回调(可以使用 immediate 立即触发)默认会立即触发
使用场景监听单个数据源监听多个数据源
代码量较多较少

停止侦听器

正常情况下,侦听器创建后会被自动绑定到宿主组件实例上,当组件实例被卸载时,侦听器也就会被停止。

但如果我们想手动停止侦听器,则需要调用 watch 或 watchEffect 的返回值函数

vue
<template>
	<button @click="stopWatch">stopWatch</button>
</template>

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

  const todoId = ref(1);

  const unWatch = watch(todoId, (newVal, oldVal) => {
    console.log("watch: " + newVal);
  });

  const unWatchEffect = watchEffect(() => {
    console.log("watchEffect: " + todoId.value);
  });

  function stopWatch() {
    unWatch();
    unWatchEffect();
  }

  setInterval(() => {
    todoId.value++;
  }, 1000);
</script>

特殊的,侦听器必须使用 同步 语句创建,如果用异步回调创建侦听器,它不会绑定到当前组件上,必须手动停止它,以防止内存泄漏。

vue
<script setup lang="ts">
  import { watchEffect } from 'vue'

  // 它会自动停止
  watchEffect(() => {})

  // 这个则不会
  setTimeout(() => {
    watchEffect(() => {})
  }, 100)
</script>

大多数情况下,我们不需要异步创建侦听器 ,如果真要异步使用侦听器,可以增加条件式的侦听逻辑。

vue
<script setup lang="ts">
  // 需要异步请求得到的数据
  const data = ref(null)

  watchEffect(() => {
    if (data.value) {
      // 数据加载后执行某些操作
    }
  })
</script>

Released under the MIT License.