引入
上节已经把响应式基本架子已经搭建出来,但还是有一些比较隐式的问题存在
- 分支切换问题
- effect嵌套问题
- 递归调用
这节我们主要针对分支切换的问题来进行解决
问题引入
- 上一节的整体代码
/**
* 双向绑定
* 响应式系统的简单实现
*/
let obj = {
name: "zs",
age: 15,
};
let activeEffectFn = null;
const bucket = new WeakMap();
function track(target, key) {
// 每个对象下面对应的属性都有一个副作用函数
let deps = bucket.get(target);
if (!deps) {
bucket.set(target, (deps = new Map()));
}
let effectFns = deps.get(key);
if (!effectFns) {
deps.set(key, (effectFns = new Set()));
}
effectFns.add(activeEffectFn);
}
function trigger(target, key) {
let deps = bucket.get(target);
if (!deps) return;
let effectFns = deps.get(key);
effectFns.forEach((effectFn) => {
effectFn();
});
}
let handle = {
get(target, key, receiver) {
// 在get中进行track收集依赖函数
track(target, key);
return target[key];
},
set(target, key, newValue, receiver) {
target[key] = newValue;
trigger(target, key);
return true;
},
};
function effect(fn) {
// 执行effect的fn 会导致执行proxy的get
activeEffectFn = fn;
fn();
}
effect(() => {
console.log("effect");
if (obj.ok) {
console.log(obj.name);
}
});
obj.ok = false;
// 改变name的时候也会触发也会打印effect
obj.name = "ls";
- 问题点引出 分支切换
- 当ok为true的时候 副作用函数中应该收集的依赖是ok和name
- 当ok为false的时候副作用函数收集的依赖应该只是ok
- 所以问题来了无论ok为true还是false 我们都会收集name对应的副作用函数,当ok为false改变name的时候也会触发effect执行,这样就导致多余收集的问题
- 解决思路就是当我们每次trigger的时候effect中会触发get 就是属性会重新进行依赖收集 这时候我们就可以先清除原来收集过得副作用函数 当执行effect的时候在重新收集 关键函数为cleanup
解决思路
-
当我们每次trigger的时候
1 先执行effect 2 cleanup 清除副作用函数 3 执行传入effect中的函数触发get 然后触发track 4 重新收集依赖
关键函数
// 清除分支
function cleanup(activeFn) {
for (let i = 0; i < activeFn.deps.length; i++) {
let deps = activeFn.deps[i]; //这个是桶里面的set 每个属性下set里面包含的是它对应的副作用函数
deps.delete(activeFn);
}
console.log(JSON.parse(JSON.stringify(bucket)));
activeFn.deps.length = 0;
}
function effect(effectFn) {
// 执行effect的fn 会导致执行proxy的get
function activeFn() {
cleanup(activeFn);
activeEffectFn = activeFn;
effectFn();
}
activeFn.deps = [];
activeFn();
}
function track(target, key) {
// 每个对象下面对应的属性都有一个副作用函数
// ...xxx
activeEffectFn.deps.push(effectFns);
}
整体代码
/**
* 双向绑定
* 响应式系统的简单实现
*/
let obj = {
name: "zs",
age: 15,
};
let activeEffectFn = null;
const bucket = new WeakMap();
function track(target, key) {
// 每个对象下面对应的属性都有一个副作用函数
let deps = bucket.get(target);
if (!deps) {
bucket.set(target, (deps = new Map()));
}
let effectFns = deps.get(key);
if (!effectFns) {
deps.set(key, (effectFns = new Set()));
}
effectFns.add(activeEffectFn);
}
function trigger(target, key) {
let deps = bucket.get(target);
if (!deps) return;
let effectFns = deps.get(key);
effectFns.forEach((effectFn) => {
effectFn();
});
}
let handle = {
get(target, key, receiver) {
// 在get中进行track收集依赖函数
track(target, key);
return target[key];
},
set(target, key, newValue, receiver) {
target[key] = newValue;
trigger(target, key);
return true;
},
};
function effect(fn) {
// 执行effect的fn 会导致执行proxy的get
activeEffectFn = fn;
fn();
}
let proxyObj = new Proxy(obj, handle);
// 问题点引出 分支切换
// 当ok为true的时候 副作用函数中应该收集的依赖是ok和name
// 当ok为false的时候副作用函数收集的依赖应该只是ok
// 所以问题来了无论ok为true还是false 当我改变那么的时候都会触发副作用函数这是错误的
// 这是因为第一次ok为true的时候副作用函数收集的是ok和name 这两个属性下面都有副作用函数 所以当ok变成false的时候会触发副作用函数
// 解决思路就是当我们每次trigger的时候effect中会触发get 就是属性会重新进行依赖收集 这时候我们就可以进行清除一下属性的分支 把我们没有访问到的属性从桶里面给清除出去 关键函数为cleanup
effect(() => {
console.log("effect");
if (obj.ok) {
console.log(obj.name);
}
});
obj.ok = false;
obj.name = "ls";