回顾之前的用例
effect.spec.ts
it("stop", () => {
let dummy;
const obj = reactive({ prop: 1 });
const runner = effect(() => {
dummy = obj.prop;
});
obj.prop = 2;
expect(dummy).toBe(2);
// 使用了stop,删除了当前依赖
stop(runner);
obj.prop = 3;
expect(dummy).toBe(2);
// stopped effect should still be manually callable
runner();
expect(dummy).toBe(3);
});
如果把 obj.prop = 3 改成 obj.prop++
it("stop", () => {
let dummy;
const obj = reactive({ prop: 1 });
const runner = effect(() => {
dummy = obj.prop;
});
obj.prop = 2;
expect(dummy).toBe(2);
// 使用了stop,删除了当前依赖
stop(runner);
obj.prop++;
expect(dummy).toBe(2);
// stopped effect should still be manually callable
runner();
expect(dummy).toBe(3);
});
报错了? 为什么?思考一下这个问题
- obj.prop = 3 只触发了set操作,我们在前面已经用stop去清除对应的effect,所以不会有变化。
- obj.prop++ 等于 obj.prop = obj.prop + 1 ,既触发了get,又触发了set,触发get又会把依赖收集,set的时候又会触发。
既然找到了原因,那着手解决! effect.ts
/** 看得见的思考
* 1. 在track的时候加个判断标识,如果判断到该标识为true,就不进行收集
* 2. 调用实例的run时,会track,我们在run里给判断标识赋值
*/
class Effect {
private _fn: any;
scheduler?: Function | undefined;
stopFlag = false;
onStop?: () => void;
// 收集所有的dep
depMap = [];
constructor(fn) {
this._fn = fn;
}
// 执行effect接收的fn
run() {
// 如果执行了stop,不会继续进行收集。
+ if (this.stopFlag) {
+ return this._fn();
+ }
+ // 保存当前实例,给track收集
+ activeEffect = this;
+ // 初始化收集状态
+ shouldTrack = true;
+ const result = this._fn();
+ /** 看得见的思考
+ * 1. fn调用(track)后,重置收集状态
+ * 2. 避免下一轮fn(track的时候),如果shouldTrack为true,还会被收集进去
+ * 3. track内部判断了shouldTrack,所以要在track后重置收集状态
+ */
+ shouldTrack = false;
+ return result;
}
/** 清除当前effect
* 1. 把所有的dep存起来,再从dep中清除当前的effect
* 2. 把当前effect从对应的dep中删除,触发依赖的时候就遍历不到该数据
*/
stop() {
this.onStop && this.onStop();
// 避免多次调用
if (!this.stopFlag) {
cleanEffect(this);
this.stopFlag = true;
}
}
}
export function track(target, key) {
+ if (!isTracking()) return;
let depMap = targetMap.get(target);
if (!depMap) {
depMap = new Map();
targetMap.set(target, depMap);
}
let dep = depMap.get(key);
if (!dep) {
dep = new Set();
depMap.set(key, dep);
}
+ // 提取了收集函数
+ trackEffect(dep);
}
+ export function isTracking() {
+ // shouldTrack为true并且当前实例不为undefined,就会进行依赖收集
+ return shouldTrack && activeEffect !== undefined;
+ }
+ export function trackEffect(dep) {
+ // 收集当前不存在的实例
+ !dep.has(activeEffect) && dep.add(activeEffect);
+ // 收集当前的dep
+ activeEffect.depMap.push(dep);
+ }
修改为测试
yarn test effect