认识
一、认识
Vue.js 3.0
中, watch
用于监测响应式数据, 当数据发生变化时, 通知并执行相应的回调函数。watch
传入的第一个参数为要监测的响应式数据, 读取数据并包装成一个副作用函数,创建一个带有 scheduler
的 effect
。当响应式数据发生变化时, 由于有 scheduler
, 会触发 scheduler
调度函数执行,而不是直接触发副作用函数执行。在 scheduler
调度函数中, 执行 watch
传入的回调。watch
机制如下所示:
function watch(source,cb,options = {}){
effect(
()=> source, // 读取响应式数据, 触发响应式数据的 `get`, 建立联系
{
scheduler(){
cb(); // 当响应式数据发生变化时, 调用回调函数 cb
}
}
)
}
二、变量细节
2.1 getter
2.2 cleanup
watch
提供清除过期副作用的能力, 这个能力的实现通过 cleanup
。基本逻辑如下:
function watch(source,cb,options = {}){
let cleanup;
function onInvalidate(fn){
cleanup = fn;
}
const job = ()=>{
newValue = effectFn();
if(cleanup){
cleanup();
}
cb(newValue,oldValue,onInvalidate);
oldValue = newValue;
}
}
如上所示, 通过 cleanup
存储用户注册的过期回调, 每当 watch
回调函数执行之前, 会优先执行用户通过 onInvalidate
注册的过期回调。 这样, 用户就有机会在过期回调中将上一次的副作用标记为 过期, 从而以 忽略请求数据的方式 解决竞态问题。调用方式如下所示:
function request() {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000);
});
}
watch(
() => obj.c,
async (newValue, oldValue, onInvalidate) => {
let expired = false;
onInvalidate(() => {
expired = true;
});
await request();
if (!expired) {
console.log(newValue, oldValue);
}
}
);
obj.c++;
setTimeout(() => {
obj.c++;
}, 200);
三、函数细节
3.1 job
job
为 scheduler
函数中抽离的主要逻辑, 基本实现如下:
const job = ()=>{
newValue = effectFn();
cb(newValue,oldValue);
oldValue = newValue;
}
3.2 scheduler
scheduler
为 effect
的调度器, 可以控制副作用函数的执行顺序与执行次数。如果 effect
存在 scheduler
配置项, 当响应式数据发生变化时, 会触发 scheduler
调度函数执行, 而不是直接触发副作用函数。机制如下:
effect(
()=>{ console.log(obj.a); },
{
scheduler(fn){
fn();
}
}
);
四、问题
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
一样, 是一个新的状态数据, 并具有延时计算和缓存计算结果的功能。