7. Watch & WatchEffect
7. Watch & WatchEffect
1.watch 基本使用
watch 的核心就是观测一个响应式数据,当数据变化时通知并执行回调 (那也就是说它本身就是一个 effect)
watch(state, (oldValue, newValue) => {
// 监测一个响应式值的变化
console.log(oldValue, newValue);
});
2.监测响应式对象
reactive.ts
export function isReactive(value) {
return !!(value && value[ReactiveFlags.IS_REACTIVE]);
}
apiWatch.ts
export function watch(source, cb, options = {}) {
return doWatch(source, cb, options as any);
}
function doWatch(source, cb, { deep }) {
let getter;
// 将对象转化成getter函数
const reactiveGetter = (source) =>
traverse(source, deep === false ? 1 : undefined);
// 如果是响应式对象
if (isReactive(source)) {
getter = () => reactiveGetter(source); // 根据深度创建getter
}
}
// 遍历属性,会触发proxy中的get方法
function traverse(value, depth, currentDepth = 0, seen = new Set()) {
if (!isObject(value)) {
return value;
}
if (depth) {
// 记录遍历的深度
if (currentDepth >= depth) {
return value;
}
currentDepth++;
}
if (seen.has(value)) {
return value;
}
seen.add(value);
for (const k in value) {
// 递归访问属性用于依赖收集
traverse(value[k], depth, currentDepth, seen);
}
return value;
}
3.创建 effect
let oldValue;
const job = () => {
if (cb) {
const newValue = effect.run();
cb(newValue, oldValue);
oldValue = newValue;
}
};
// 创建watch对应的effect
const effect = new ReactiveEffect(getter, () => {}, job);
oldValue = effect.run(); // 运行保存老值
4.监测函数
function doWatch(source, cb, { deep }) {
let getter;
if (isReactive(source)) {
// 如果是响应式对象
getter = () => traverse(source);
} else if (isFunction(source)) {
getter = source; // 如果是函数则让函数作为fn即可
}
// ...
}
5.watch 中回调执行时机
function doWatch(source, cb, { deep, immediate }) {
const effect = new ReactiveEffect(getter, () => {}, job);
if (cb) {
if (immediate) {
job();
} else {
oldValue = effect.run(); // 运行保存老值
}
}
}
6.watch 中 cleanup 实现
连续触发 watch 时需要清理之前的 watch 操作
const state = reactive({ flag: true, name: 'erxiao', age: 30 });
let i = 2000;
function getData(timer) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(timer);
}, timer);
});
}
watch(
() => state.age,
async (newValue, oldValue, onCleanup) => {
let clear = false;
onCleanup(() => {
clear = true;
});
i -= 1000;
let r = await getData(i); // 第一次执行1s后渲染1000, 第二次执行0s后渲染0, 最终应该是0
if (!clear) {
document.body.innerHTML = r;
}
},
{ flush: 'sync' }
);
state.age = 31;
state.age = 32;
let cleanup;
let onCleanup = (fn) => {
// 保存用户传入的清理函数
cleanup = () => {
fn();
cleanup = undefined;
};
};
const job = () => {
if (cb) {
const newValue = effect.run();
if (cleanup) {
cleanup();
}
// 下次回调执行前,调用清理函数
cb(newValue, oldValue, onCleanup);
oldValue = newValue;
}
};
7.停止 watch
const unwatch = () => {
effect.stop();
};
return unwatch;
stop() {
if (this.active) { // 清理掉所有依赖
preCleanupEffect(this);
postCleanupEffect(this);
this.active = false;
}
}
8.watchEffect
我们可以使用响应性属性编写一个方法,每当它们的任何值更新时,我们的方法就会重新运行。
watchEffect
在初始化时也会立即运行
const state = reactive({ flag: true, name: 'erxiao', age: 30 });
watchEffect(() => (app.innerHTML = state.name));
setTimeout(() => {
state.name = 'Mr erxiao';
}, 1000);
export function watch(source, cb, options) {
return doWatch(source, cb, options);
}
export function watchEffect(effect, options) {
return doWatch(effect, null, options);
}
const job = () => {
if (cb) {
// ...
} else {
effect.run(); // watchEffect重新执行
}
};
const effect = new ReactiveEffect(getter, () => {}, job);
if (cb) {
// ...
} else {
effect.run(); // watchEffect
}