持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天,点击查看活动详情
4.3 设计一个完善的响应式系统
上文有一点忘写了,就是对代码的封装。我们在proxy的get,set方法中实现了数据的响应,这时候,我们最好对代码进行一定封装,这样可以更方便后续的使用和迭代。
const effectStore = new WeakMap()
let activeEffect
//在get时调用
const track = (target,key)=>{
if(!activeEffect){retruen}
let deps = effectStore.get(key)
if(!deps){
deps.set(key,(deps = new Set()))
}
deps.add(activeEffect)
}
//在set时调用
const trigger = (target,key)=>{
const deps = effectStore.get(key)
deps && deps.forEach(fn=>fn())
}
这时候我们发现,我们的proxy永远只能对应一个target对象,但是99.99%的时候,我们需要同时响应多个对象,那么如何实现呢?
其实就像effectStore一样,我们再次建立一个target的对应关系,这时候可不能用WeakMap了。
在这个对应关系中,key应该是target,也就是原始数据,而值则是我们被代理的对象,也就是我们的数据。
因此,我们这样修改代码,再给他添加一重依赖。
const depsMap = new WeakMap()
const targetMap = new Map()
let activeEffect
//在get时调用
const track = (target,key)=>{
if(!activeEffect){retruen}
let depsMap = targetsMap.get(target);
if (!depsMap) {
targetsMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
//在set时调用
const trigger = (target,key)=>{
let depsMap = targetsMap.get(target);
if (!depsMap) {
return;
}
let deps = depsMap.get(key);
deps && deps.forEach(fn=>fn())
}
其实这里逻辑就有点复杂了,很多人到这一步就会被依赖搞混了。 首先,这里有一个Map,这个map储存的是数据之间的对应关系,然后每次先找对应数据,再通过key去找副作用函数,这样就实现了同时响应多个数据。
在之前的代码里,我们总是赋值了一个data,用了一个proxy。但是实际开发中,我们会有很多个data,因此我们需要把他们之际的对应关系储存起来。
到这里,其实我们已经可以实现一个简单的reactive了,他有着和vue/reactive一样的api。
全部代码如下,如有错误,请留言指正
//这里为了方便debbug,把它放到了window下
window.targetsMap = new WeakMap();
let activeEffect = null;
// 执行effect函数,也就是更新数据
const effect = (func) => {
activeEffect = func;
func && func();
activeEffect = null;
};
// 添加依赖追踪
const track = (target, key) => {
if (!activeEffect) {
return;
}
let depsMap = targetsMap.get(target);
if (!depsMap) {
targetsMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
};
// 触发更新
const trigger = (target, key) => {
let depsMap = targetsMap.get(target);
if (!depsMap) {
return;
}
let dep = depsMap.get(key);
if (dep) {
dep.forEach((fn) => {
fn();
});
}
};
//对于引用类型的响应式
const reactive = (target) => {
const handler = {
get(target, key, receiver) {
console.log('get handler called for', key);
const result = Reflect.get(target, key, receiver);
track(target, key);
return result;
},
set(target, key, newValue, receiver) {
console.log('set handler called for', key, '=', newValue);
const oldValue = target[key];
const result = Reflect.set(target, key, newValue, receiver);
if (result && oldValue !== newValue) {
trigger(target, key);
}
return result;
},
};
return new Proxy(target, handler);
};