尤雨溪教你写进阶版响应式
次访问
前言
上一篇,我们解析了尤雨溪的初级版响应式代码,一定觉得初级版的响应式和我们平常用的vue3相差太远了,定义一个响应式对象,竟然还要手写get和set,而我们日常使用vue3来定义一个响应式对象只需要用ref或者reactive包起来。
那么,这一篇我们看看尤雨溪对初级版做了哪些升级。
封装reactive
上一版中,定义响应式变量需手写get和set:
1 | const state = { |
那么这一版首先就将这一步封装起来。
1 | function reactive(raw) { |
封装后,定义响应式变量只需要:
1 | const state = reactive({ |
现在看来,是不是和vue3的写法一样了呢?
接下来我们解读下这个优化。
首先翻译几个名词,为什么想翻译一下呢?因为我觉得尤雨溪写的代码命名非常好,一段代码读下来,就像看小说,即使不懂编程,也会大概知道是做什么。
reactive: 反应的
raw: 未加工的、不成熟的
这两个变量名取的好呀,一眼看过去,函数reactive接受一个未加工的变量raw,然后返回了raw。咱们不看内容,就可以猜到reactive函数做了什么。
下面我们来看看reactive函数的内容(代码中的英文注释也是尤雨溪写的哦)。
传进来的raw对象,我们对key进行遍历,取得key对应的value值后,立即重写raw。
这里用到Object.defineProperty(obj, prop, descriptor),这个方法会直接在一个对象obj上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。其中obj是要定义属性的对象,prop是要定义或修改的属性的key,descriptor是更新或定义的属性的描述。
所以这里重写时,给key值对应的value绑定了get和set函数,实现了上一版手动绑定的过程。
然后优化watchEffect,执行effect后立刻将activeEffect置空,防止不必要的订阅行为。
1 | function watchEffect(effect) { |
再次进阶
上面的优化比较小,只是简化了响应式变量的定义,接下来我们看看尤雨溪还能做什么优化。
首先,重写了Dep类。
之前的Dep类比较简单,只有一个发布订阅模式。
1 | let activeEffect |
优化后:
1 | let activeEffect |
多了一个叫_value的私有变量,并且这个_value是响应式的。然后写了一个reactiveHandlers函数:
1 | // proxy version |
接着是getDep函数:
1 | const targetToHashMap = new WeakMap() |
最后就是用Proxy替代defineProperty重写reactive函数:
1 | function reactive(obj) { |
这里补充下Proxy知识点:
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
语法是const p = new Proxy(target, handler)
,
target是要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
示例:
1 | const handler = { |
补充完Proxy知识点,我们再去看reactive。
我们用reactiveHandlers代理obj,读写obj时会进入reactiveHandlers,读的时候,通过getDep获取value值,如果value是object类型,就将value变成响应式,然后返回value;写的时候就把新值写到getDep获取的value上。
在getDep函数中,用到了Map和WeakMap。
Map对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。
WeakMap对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
getDep中,首先去targetToHashMap中获取target,并赋值给depMap,没有获取到的话,就将target作为键,new Map()生成的depMap作为值,存到targetToHashMap中。
然后在depMap中取key的值,取不到的话,就new Dep(target[key])作为value和key一起,组成键值对,加到depMap中。最后返回dep。
使用还是一样:
1 | const state = reactive({ |
在上面的代码中加上一些打印,使代码流程看得更加清楚:
可见getDep最后返回了一个Dep的实例。
还有什么不清楚的地方,自行断点调试。
结语
所以,看到这里,你明白Proxy比起defineProperty,在实现vue的响应式时,有什么好处吗?
后期后空,我们继续学习尤雨溪会怎么实现一个mini vue。