next响应式原理(2)

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; }

同样,这里使用 Reflect 来对 target 进行赋值操作,因为它会返回一个 boolean 值代表是否成功,而 set 这个 trap 也需要代表相同含义的值。

通过 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 处理逻辑

代理实例:缓存这些代理实例,下次遇到直接返回即可

但我仍然推荐你直接去阅读一下源码,因为你会发现,源码会在这个极简版本基础上,利用了更加复杂数据结构以及流程,来控制依赖收集和触发响应的流程,同时各种特殊情况也有更加明细的考虑。

另外,这仅仅是 vue-next 响应式系统的简易实现,诸如其他功能模块,比如指令、模板解析、vdom 等,我也准备利用最近的空闲时间再去看看,有时间的话,最近也整理出来,分享给大家。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/abd37be9172cb0c706a5b5909ea04367.html