watch
一、认识
watch
侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。与 watchEffect()
相比,watch()
使我们可以:
-
懒执行副作用
-
更加明确是应该由哪个状态触发侦听器重新执行
-
可以访问所侦听状态的前一个值和当前值。
二、语法
watch(
source: Ref | reactive | object | getter | (Ref | reactive | object | getter) []
(newValue,oldValue,onCleanup)=>{},
options?: {
immediate?: boolean,
deep?: boolean,
flush?: 'pre' | 'post' | 'sync',
onTrack?: (event)=> void
onTrigger?: (event)=> void
})
-
source
: 侦听器的源。这个来源可以是以下几种:-
一个函数,返回一个值
-
一个
ref
-
一个响应式对象
-
...或是由以上类型的值组成的数组
-
-
callback
: 在发生变化时要调用的回调函数。这个回调函数接受三个参数: 新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。 -
options
: 可选, 是一个对象,支持以下这些选项:-
immediate
: 在侦听器创建时立即触发回调。第一次调用时旧值是undefined
。watch()
默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。 -
deep
: 如果源是对象,强制深度遍历,以便在深层级变更时触发回调 -
flush
:post | sync
。调整回调函数的刷新时机 -
onTrack / onTrigger
: 调试侦听器的依赖
-
三、用法
3.1 侦听 Ref
const a = ref(0);
watch(a, (newValue, oldValue) => {
console.log(newValue, oldValue);
});
a.value += 1;
3.2 侦听 Reactive
侦听整个 Reactive
import { reactive, watch } from 'vue';
const b = reactive({ c: 1 });
watch(b, (newValue, oldValue) => {
console.log(newValue, oldValue);
});
b.c++;
侦听 Reactive
具体属性
import { reactive, watch } from 'vue';
const b = reactive({ c: 1 });
watch(b.c, (newValue, oldValue) => {
console.log(newValue, oldValue);
});
b.c++;
3.3 侦听 getter
当使用 getter
作为数据源时, 回调函数只在此函数的返回值发生变化时才会触发。
import { ref, reactive, watch } from 'vue';
const a = ref(0);
const b = reactive({ c: 1 });
watch(
() => a.value + b.c,
(newValue, oldValue) => {
console.log(newValue, oldValue);
}
);
a.value += 1;
3.4 侦听多个数据源
侦听多个数据源时, newValue
和 oldValue
与 source
对应, 也是两个数组, 分别为: newValue = [数据源新值]
、oldValue = [数据源旧值]
import { ref, reactive, watch } from 'vue';
const a = ref(0);
const b = reactive({ c: 1 });
watch([a, b], (newValue, oldValue) => {
console.log(newValue, oldValue);
});
a.value += 1;
3.5 停止侦听器
import { ref, watch } from 'vue';
const a = ref(0);
const stop = watch(a, (newValue, oldValue) => {
console.log(newValue, oldValue);
});
a.value += 1; // 触发 watch 回调
setTimeout(() => {
stop();
}, 2000);
a.value += 1; // 不会触发 watch 回调
3.6 开启深层侦听
3.7 清除过期副作用
通过标记的方式清除过期副作用, 解决竞态问题: 只处理最后一次的请求响应数据, 进而解决竞态问题
import { ref, watch } from 'vue';
const a = ref(0);
function request() {
return new Promise(resolve => {
setTimeout(() => {
resolve(true);
}, 2000);
});
}
watch(a, async (newValue, oldValue, onCleanup) => {
let expired = false;
onCleanup(() => {
expired = true;
});
await request();
if (!expired) {
console.log(newValue, oldValue);
}
});
a.value += 1;
setTimeout(() => {
a.value += 1;
}, 1000);
通过取消请求的方式清除过期副作用, 解决竞态问题: 通过取消之前请求, 进而解决竞态问题
import { ref, watch } from 'vue';
const a = ref(0);
function request() {
return new Promise(resolve => {
setTimeout(() => {
resolve(true);
}, 2000);
});
}
watch(a, async (newValue, oldValue, onCleanup) => {
const { response, cancel } = doAsyncWork(newValue)
onCleanup(() => {
cancel(); // 取消之前请求
});
await request(); // 始终只发送最新请求
});
a.value += 1;
setTimeout(() => {
a.value += 1;
}, 1000);
3.8 收集、管理副作用
import { ref, watch, watchEffect } from 'vue';
let disposables = [];
const count = ref(0);
const watchCount = watch(()=> count.value, ()=> console.log("watch count", count.value));
disposables.push(watchCount);
const watchEffectCout = watchEffect(()=> console.log('watchEffect count', count.value));
disposables.push(watchEffectCout);
setTimeout(()=>{
count.value++;
},2000);
setTimeout(()=>{
disposables.forEach(dispose=> dispose());
disposables = []
},4000);
setTimeout(()=>{
count.value++;
}, 60000);
3.9 指定回调函数立即执行
3.10 指定回调函数执行时机
四、问题
4.1 computed vs watch
watch
用于监测响应式数据, 当数据发生变化时, 通知并执行相应的回调函数。watch
传入的第一个参数为要监测的响应式数据, watch
中创建一个 effect
副作用, 第一个参数为封装好副作用函数, 这个副作用函数主要是读取传入的响应式数据, 触发响应式数据的副作用收集机制, 传入 Scheduler
配置项, 在 effect
中, 如果有 Scheduler
配置项, 那么执行 Scheduler
调度函数执行, 而不是直接触发副作用函数执行。在 Scheduler
调度函数中, 执行传入的 watch
回调。watch
有三个参数, 侦听器的源, 发生变化的回调, 和一个配置项, 可以配置 immediate
立即监听, 可以配置 deep
进行深度监听。 在变化的回调中, 同样提供了三个参数, 最新值, 旧值, 以及一个用于注册副作用清理的回调函数 cleanup
。 cleanup
可以用于解决一些竟态问题, 在响应式数据发生变化后, 调用 cleanup
, 并传入想要取消的逻辑代码, 优先取消之前的副作用影响后,再开始新的副作用。
computed
是一个计算属性, 根据传入的 getter
函数, 计算得到一个可读或者可读可写的响应式数据。 computed
本质上是将传入的 getter
作为一个副作用函数, 创建一个带有 lazy
和 scheduler
的 effect
, 返回一个具有访问器属性value
和 设置器属性value
的数据。在读取 computed
数据时, 访问器进行拦截, 在内部变量 dirty
为 true
的情况下, 执行通过 effect
返回的 effectFn
函数, 这个 effectFn
内部会执行 computed
传入的 getter
进行计算。因此, computed
同 ref
一样, 是一个新的状态数据, 并具有延时计算和缓存计算结果的功能。