禁止转载,侵权必究!
Watch 实现原理
watch 的核心就是观测一个响应式数据,当数据变化时通知并执行回调 (那也就是说它本身就是一个 effect)
1.监测响应式对象
3.watch.html script
const state = reactive({ flag: false, name: "lx", age: 18, n: { n: 1 } });
// 数据变化后就调用回调方法
// 常见的两种方式 直接监控某个对象 或者 监控某个属性
// 直接监控对象 不建议 性能差
// watch 就是effect, 状态会收集watch effect, 属性变化后 会触发scheduler
watch(
() => state.name,
(newValue, oldValue) => {
// 对象是引用地址 无法区分新的老的
console.log(newValue, oldValue);
}
/* { immediate: true } */
);
state.name = "zf";
state.name = "jw";
watch.ts
function traverse(value, seen = new Set()) {
if (!isObject(value)) {
return value;
}
if (seen.has(value)) {
return value;
}
seen.add(value);
for (const k in value) {
// 递归访问属性用于依赖收集
traverse(value[k], seen);
}
return value;
}
export function isReactive(value) {
return !!(value && value[ReactiveFlags.IS_REACTIVE]);
}
export function watch(source, cb) {
let getter;
if (isReactive(source)) {
// 如果是响应式对象
getter = () => traverse(source); // 包装成effect对应的fn, 函数内部进行遍历达到依赖收集的目的
} else if (isFunction(source)) {
getter = source; // 如果是函数则让函数作为fn即可
}
let oldValue;
const job = () => {
const newValue = effect.run(); // 值变化时再次运行effect函数,获取新值
cb(newValue, oldValue);
oldValue = newValue;
};
const effect = new ReactiveEffect(getter, job); // 创建effect
oldValue = effect.run(); // 运行保存老值
}
2.监测函数
watch.ts
export function watch(source, cb) {
let getter;
if (isReactive(source)) {
// 如果是响应式对象
getter = () => traverse(source);
} else if (isFunction(source)) {
getter = source; // 如果是函数则让函数作为fn即可
}
// ...
}
3.immediate 实现
export function watch(source, cb, { immediate } = {} as any) {
// ...
const effect = new ReactiveEffect(getter, job); // 创建effect
if (immediate) {
// 需要立即执行,则立刻执行任务
job();
}
oldValue = effect.run();
}
4.watch 中 cleanup 实现
连续触发 watch 时需要清理之前的 watch 操作
5.cleanUp.html script
const state = reactive({ flag: true, name: "jw", age: 30 });
let timer = 3000;
function getData(newVal) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(newVal);
}, (timer -= 1000));
});
}
/*
watch(
() => state.name,
async function (newValue) {
let flag = true;
while (arr.length > 0) {
let cb = arr.shift(); // 取出来数组中的回调就销毁了
cb();
}
arr.push(() => {
// 这个函数是一个闭包
flag = false;
// // 清除token
});
console.log("flag", flag);
let r = await getData(newValue);
if (flag) {
app.innerHTML = r;
}
}
);
// // 什么叫闭包 定义的作用域与执行的作用域不同;
state.name = 1;
state.name = 2;*/
watch(
state,
async (newValue, oldValue, onCleanup) => {
let clear = false;
onCleanup(() => {
clear = true;
});
let r = await getData(newValue.name);
if (!clear) {
document.body.innerHTML = r;
}
// 监测一个响应式值的变化
},
{ immediate: true }
);
state.age = 31;
state.age = 32;
effect.ts
// doWatch()
// ...
let cleanup;
let onCleanup = (fn) => {
cleanup = fn;
};
const job = () => {
const newValue = effect.run();
if (cleanup) cleanup(); // 下次watch执行前调用上次注册的回调
cb(newValue, oldValue, onCleanup); // 传入onCleanup函数
oldValue = newValue;
};
// ...
5.watchEffect
我们可以使用响应性属性编写一个方法,每当它们的任何值更新时,我们的方法就会重新运行。watchEffect 在初始化时也会立即运行
4.watchEffect.html script
const state = reactive({ flag: true, name: "jw", age: 30 });
watchEffect(() => (app.innerHTML = state.name));
setTimeout(() => {
state.name = "Mr Lix";
}, 1000);
export function watch(source, cb, options) {
return doWatch(source, cb, options);
}
export function watchEffect(effect, options) {
return doWatch(effect, null, options);
}
function doWatch(source, cb, { immediate } = {} as any) {
// ...
const job = () => {
if (cb) {
const newValue = effect.run(); // 值变化时再次运行effect函数,获取新值
if (cleanup) cleanup(); // 下次watch执行前调用上次注册的回调
cb(newValue, oldValue, onCleanup);
oldValue = newValue;
} else {
effect.run(); // 重新执行effect即可
}
};
// ...
}