watch
Vue2 watch 的工作流程
在vue2 4.初始化数据流程中,new了一些Watcher(),watch就是这些watcher
- 初始化 :为每个
watch项创建Watcher实例 - 依赖收集 :
Watcher首次执行get(),触发响应式数据的getter,将自身收集为依赖 - 深度监听 :如果设置了
deep: true,会递归遍历对象的所有属性,触发它们的getter,从而收集所有嵌套属性的依赖 - 触发更新 :当响应式数据变化时,触发
setter,调用所有依赖的update()方法 - 执行回调 :
Watcher的update()比较新旧值,如果变化则执行回调
在vue3中,watch,是对ReactiveEffect的另一种effect实现
Vue3 watch 的工作流程
在vue3 四、创建副作用中,通过ReactiveEffect创建了effect,watch是对effect的更深度的实现
-
标准化源 :将各种形式的
source(ref、reactive、getter 函数、数组)统一为getter函数 -
深度监听处理 :如果启用深度监听,会递归遍历对象,触发所有属性的
getter,收集依赖 -
创建 effect :创建
ReactiveEffect,将getter作为副作用函数 -
依赖收集 :首次执行
effect.run(),触发getter,读取响应式数据,完成依赖收集 -
调度更新 :当依赖的数据变化时,根据
flush选项决定何时执行回调'pre':组件更新前执行(默认)'post':组件更新后执行(类似 Vue2 的$nextTick)'sync':同步执行
-
执行回调 :执行用户提供的回调函数,支持副作用清理
js
// 简化版 watch 实现
function watch(source, cb, options = {}) {
const {
immediate = false,
deep = false,
flush = 'pre' // 'pre' | 'post' | 'sync'
} = options;
let getter;
let cleanup;
let oldValue;
// 1. 标准化 source 为 getter 函数
if (isRef(source)) {
// ref 类型
getter = () => source.value;
} else if (isReactive(source)) {
// reactive 对象
getter = () => source;
// reactive 对象默认深度监听
deep = true;
} else if (typeof source === 'function') {
// getter 函数
getter = source;
} else if (Array.isArray(source)) {
// 数组形式,监听多个源
getter = () => source.map(s => {
if (isRef(s)) return s.value;
if (isReactive(s)) return traverse(s);
if (typeof s === 'function') return s();
return s;
});
} else {
getter = () => {};
}
// 2. 深度监听处理
if (deep) {
const baseGetter = getter;
getter = () => traverse(baseGetter());
}
// 3. 定义作业函数
const job = () => {
if (!effect.active) return;
const newValue = effect.run();
// 清理副作用
if (cleanup) {
cleanup();
}
// 执行回调,传入清理函数
cb(newValue, oldValue, (fn) => {
cleanup = () => {
fn();
cleanup = null;
};
});
oldValue = newValue;
};
// 4. 创建调度器
let scheduler;
if (flush === 'sync') {
scheduler = job;
} else if (flush === 'post') {
scheduler = () => queuePostFlushCb(job);
} else { // 'pre'
scheduler = () => queueJob(job);
}
// 5. 创建响应式 effect
const effect = new ReactiveEffect(getter, scheduler);
// 6. 初始化
if (immediate) {
job();
} else {
oldValue = effect.run();
}
// 7. 返回停止函数
return () => {
effect.stop();
if (cleanup) cleanup();
};
}
// 深度遍历(与 Vue2 类似,但使用 Proxy)
function traverse(value, seen = new Set()) {
if (!isObject(value) || value === null || seen.has(value)) {
return value;
}
seen.add(value);
if (isRef(value)) {
traverse(value.value, seen);
} else if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
traverse(value[i], seen);
}
} else if (value instanceof Map) {
value.forEach((val, key) => {
traverse(val, seen);
});
} else if (value instanceof Set) {
value.forEach(val => {
traverse(val, seen);
});
} else {
for (const key in value) {
traverse(value[key], seen);
}
}
return value;
}
watchEffect 的实现
Vue3 还提供了 watchEffect,自动追踪其同步执行期间访问的所有响应式依赖:
js
function watchEffect(effect, options = {}) {
return doWatch(effect, null, options);
}
function doWatch(source, cb, options = {}) {
const { flush = 'pre' } = options;
let getter;
if (typeof source === 'function') {
getter = source;
}
// 响应式 effect
const runner = effect(getter, {
lazy: true,
scheduler: () => {
if (cb) {
// watch
scheduler();
} else {
// watchEffect:直接重新运行 effect
runner();
}
}
});
// 记录初始值(仅 watch 需要)
let oldValue;
if (cb) {
oldValue = runner();
} else {
// watchEffect 立即执行
runner();
}
return () => {
runner.stop();
};
}
| 特性 | watch | watchEffect |
|---|---|---|
| 监听方式 | 显式声明监听的数据源 | 自动追踪回调函数中使用的响应式依赖 |
| 回调参数 | 接收(newValue, oldValue, onCleanup) |
只接收onCleanup清理函数 |
| 初始执行 | 默认不立即执行,需设置immediate: true |
立即执行一次 |
| 返回值 | 返回新值和旧值 | 没有返回值,只能访问当前值 |
| 依赖收集 | 基于传入的数据源 | 基于回调函数执行时访问的响应式数据 |
| 适用场景 | 需要新旧值对比、精确控制监听目标 | 逻辑组合、响应式副作用聚合 |
总结
Vue的watch是通过依赖收集建立监听关系,在响应式数据变化时自动触发回调执行的侦听机制。