通过 reactive 方法来初始化代理对象
实现了数据劫持的代理逻辑之后,我们只需要在 reactive 这个方法中,返回一个代理对象的实例即可,还记的上文中我们在实现之前脑海中浮现的大致代码框架吗?
如下:
export function reactive(obj: any) {
const proxied = new Proxy(obj, {
get: function(target, key: string, receiver) {
if (currentEffect) {
if (effectMap.has(key)) {
const effects = effectMap.get(key);
if (effects.indexOf(currentEffect) === -1) {
effects.push(currentEffect);
}
} else {
effectMap.set(key, [currentEffect]);
}
} return Reflect.get(target, key, receiver);
},
set: function(target, key: string, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
if (effectMap.has(key)) {
effectMap.get(key).forEach(effect => effect());
}
return result;
}
});
return proxied;
}
依赖收集的准备工作
上文中提到了,对于依赖收集的工作,我们是有条件地进行的,即在一个 effect 中,我们才会进行收集,其他情况下的取值逻辑,我们则不会进行依赖收集,因此,effect 方法正式为了实现这点而存在的,如下:
export function effect(fn: Function) {
const effected = function() {
fn();
}; currentEffect = effected;
effected();
currentEffect = undefined;
return effected;
}
之所以实现如此简单,是因为我们这里是极简版本,不需要考虑诸如 readOnly 、异常以及收集时机等因素。可以发现,就是将传入的回调函数包裹在另一个方法中,然后将这个方法用 currentEffect 这个变量暂存,之后尝试运行一下即可。当 effect 运行完毕之后,再将 currentEffect 置空,这样就可以达到只在 effect 下进行依赖收集的目的。
运行效果
我在 codepen 上简单写了一个计数器 demo,链接如下:
https://codepen.io/littlelyon1/pen/mddVPgo
写在最后
这个极简的响应式系统虽然能用,但是有很多未考虑的因素,其实就是在上文中被我们忽略的那些前提条件,这里再列举一下,并给出源代码中的解法:
基础数据类型的处理:可以将基础数据类型封装为一个 ref 对象,其 value 指向基础数据类型的值
嵌套对象:递归进行执行代理过程即可
集合对象:编写专门的 trap 处理逻辑
代理实例:缓存这些代理实例,下次遇到直接返回即可
但我仍然推荐你直接去阅读一下源码,因为你会发现,源码会在这个极简版本基础上,利用了更加复杂数据结构以及流程,来控制依赖收集和触发响应的流程,同时各种特殊情况也有更加明细的考虑。










