本章是解决响应式系统中的出现自增时,导致的栈溢出。
栈溢出的情况:
当输入一下代码会出现栈溢出
javascript
effect(() => {
console.log(obj.foo++);
});
分析:
通过trigger debugger 可以看到每次都会重复进行依赖收集然后触发渲染,并且 effectStack 中的数据每次都是相同的effect往里面增加。 因为 obj.foo++ 其实也就是 obj.foo = obj.foo + 1 这就可以看出 这一行代码 既触发了收集依赖 又触发了执行 ,也就是同时触发 track trigger, 这就导致 track 同时将effect压入栈中, trigger 就去执行,所以每次执行都会有上一次的 effect
通过调试查看 effectStack 会越来越多,而且每次添加的都是相同的effect, 并且在执行的时候 const effectsToRun = new Set(effects); 每次都是添加相同的 effects,这就导致每次根据 effectsToRun 执行的时候都会有effect
解决问题:
那么在添加 effect 的时候,判断是否不与当前执行的effect 是否与 activeEffect 相同,如果不相等才会继续 push并执行。
javascript
function trigger(target, key) {
const depsMap = bucket.get(target);
if (!depsMap) return;
const effects = depsMap.get(key);
debugger
// const effectsToRun = new Set(effects); // 新增
// effectsToRun.forEach((effectFn) => effectFn()); // 新增
const effectsToRun = new Set();
effects &&
effects.forEach((effectFn) => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn);
}
});
effectsToRun.forEach((effectFn) => effectFn());
}
完整代码:
js
const data = { foo: 1 };
let activeEffect;
const effectStack = [];
const bucket = new WeakMap();
export function effect(fn) {
const effectFn = () => {
cleanup(effectFn);
activeEffect = effectFn;
effectStack.push(effectFn);
fn();
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
};
effectFn.deps = [];
effectFn();
}
export const obj = new Proxy(data, {
get(target, key) {
track(target, key);
return target[key];
},
set(target, key, newVal) {
target[key] = newVal;
trigger(target, key);
return true;
},
});
function track(target, key) {
if (!activeEffect) return target[key];
let depsMap = bucket.get(target);
if (!depsMap) {
bucket.set(target, (depsMap = new Map()));
}
let deps = depsMap.get(key);
if (!deps) {
depsMap.set(key, (deps = new Set()));
}
deps.add(activeEffect);
activeEffect.deps.push(deps);
}
function trigger(target, key) {
const depsMap = bucket.get(target);
if (!depsMap) return;
const effects = depsMap.get(key);
// 解决栈溢出问题
const effectsToRun = new Set(effects); // 新增
effectsToRun.forEach((effectFn) => effectFn()); // 新增
// const effectsToRun = new Set();
// effects &&
// effects.forEach((effectFn) => {
// if (effectFn !== activeEffect) {
// effectsToRun.add(effectFn);
// }
// });
// effectsToRun.forEach((effectFn) => effectFn());
}
function cleanup(fn) {
fn && fn.deps.forEach((dep) => dep.delete(fn));
fn.deps.length = 0;
}