侦听器
基本使用
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 的区别?
watch | watchEffect | |
---|---|---|
数据源 | 只追踪明确指定的数据源 | 自动追踪使用到的数据源 |
触发回调 | 默认只在数据源发送变化时触发回调(可以使用 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>