目标
处理一些关于 stop 的 edge case
在 obj.prop ++ 时,会同时触发 getter 和 setter,因为 getter 会导致重新依赖收集。导致 stop 失败
it('stop', () => {
let dummy;
const obj = reactive({ foo: 1 });
const runner = effect(() => {
dummy = obj.prop;
});
obj.prop = 2;
expect(dummy).toBe(2);
stop(runner);
obj.prop = 3;
expect(dummy).toBe(2);
// ******* bug
obj.prop ++; // obj.prop = obj.prop + 1; get -> set
expect(dummy).toBe(2);
// stopped effect should still be manually callable
runner();
expect(dummy).toBe(4);
});
优化
可以对依赖收集 track 时做一些文章
let activeEffect;
let shouldTrack;
class EffectReactive {
private _fn;
deps = [];
active = true;
onStop?: () => void;
constructor(fn, public scheduler?) {
this._fn = fn;
}
run() {
// 1. 会收集依赖
// 使用 shouldTrack 来区分
if (!this.active) {
return this._fn();
}
shouldTrack = true;
activeEffect = this;
const result = this._fn();
// reset
shouldTrack = false;
return result;
}
stop() {
if (this.active) {
cleanupEffect(this);
if (this.onStop) {
this.onStop();
}
this.active = false;
}
}
}
function cleanupEffect(effect) {
effect.deps.forEach((dep: any) => {
dep.delete(effect);
});
// dep 都清空了,对应的 effect.deps 就没有意义,可清空作优化
effect.deps.length = 0;
}
export function track(target, key) {
// 重构
// if (!activeEffect) return;
// if (!shouldTrack) return;
if(!isTracking()) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
// 已经在 dep 中
if (dep.has(activeEffect)) return;
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
function isTracking() {
return shouldTrack && activeEffect !== undefined;
}