我的ref调试之旅
通过调试,一窥ref()
、reactive()
的区别在哪?为什么vue3
不使用Object.defineProperty()
实现响应式了?
基础类型初始、set、get流程
ref()初始赋值之旅,它是如何把一个基础类型变为响应式的
-
首先我再
ref
处断住,接下来让我们一窥究竟! -
实际
ref
内部调用了createRef
,而createRef
接收两个值,第一个形参rawValue
是我们传进来的hello
,第二个形参shallow
是是否浅层监听,ref
内部明显帮我们传了false
,令其深度监听!createRef
内部调用了isRef
,isRef
在官网上就有说明,用来判断一个对象是否是Ref对象,createRef
做的是,是Ref对象你就回去,不是我就把你变成我的的人! -
isRef
原来是这么判断是否是Ref对象的,每个Ref对象在出身的时候应该都背上了一个.__v_isRef
的胎记
,我们调试完ref
的实现本身应该就能看到它本身诞生的过程! -
现在我们的
isRef
检测不通过,我们有一句话是这么说的,你是不是直男?不是?不是我们就把你掰直!接下来就需要进入RefImpl
这个class
-
RefImpl
帮我们构造一个ref对象
{
__v_isShallow: false, // 是否浅层监听
dep: undefined, // 咱也不知道是啥,猜测是深度
__v_isRef: true, // 打上了ref对象胎记
_rawValue: "hello vue", //原本的值
}
5-1. toRaw
干了什么?它会一致递归寻找最初传进ref()
的value
直到.__v_raw
为undefined
,那就是找到了非ref()
包裹时最原始的值,当然.__v_raw
只针对引用类型才生效,针对原始值不生效
5-2. toReactice()
干了什么?先判断是否是引用类型,如果是就去调用reactive()
方法去转换,如果不是引用类型,返回传入值,传入值是什么?就是我们调用ref()
传的那玩意
- 至此,我们的
ref()
初始化完成了
我们直到了官网那些平时看起来八竿子都打不到的api:isRef
、toRaw
干了什么,同时也获得了一个重要的信息!就算是使用ref()
创建响应式数据,传入的如果是引用类型,也要去调用reactive()
基础类型ref对象set之旅
-
继续回到
RefImpl
这个class
,这次探索set value(newVal)
方法 1-1. 先通过isShallow()
和isReadonly
判断是否浅层监听或者是否是只读,这也是为什么shallowRef
无法监听到深层改变的原因 1-2. 如果是的话,还是直接使用newVal
1-3. 如果不是就通过toRaw()
获取响应式前的值,因为传入的是string
,toRaw()
直接返回string
-
接着进入
hasChanged()
里面对比Ref对象的初始值是否有变化,因为原始值变更为另一个原始值,内存地址变更了,所以返回true
-
接下来的操作跟之前初始化时一样,记录下响应式化之前的值,以及响应式后的值
-
进入
triggerRefValue()
,它接收两个参数,一个是现在的ref对象,一个是最新传入的值。由于我们现在还未渲染也没有别的地方依赖该ref对象,所以不会触发任何的effect
(副作用)就是进来逛了下街,但是现在也可以看出来dep
是干嘛的了,70%的概率就是监听的深度,决定后续递归深度(猜测) -
至此我们的原始值的ref对象的
set
就执行完了
基础类型ref对象get之旅
-
get
简单粗暴,内部只是去执行trackRefValue()
-
由于
trackRefValue()
内部的一个自由变量shouldTrack
为false
,所以本质还是进来逛了圈街 -
最后灰溜溜的带上当前的
value
返回了
引用类型ref()初始、set、get
引用类型ref()的初始化之旅
-
前面与基础类型初始化一致,在这
toReactive()
走向另一条路,进入reactive()
-
reactive()
内部调用createReacticeObject()
去创建引用类型的响应式对象!形参1:我们传入的对象;形参2:是否只读
型参3:指向一套引用类型ref对象内部使用的
get
、set
、delete
的公共方法
const mutableHandlers = {
get: get$1, // 指向 createGetter
set: set$1, // 指向 createSetter
deleteProperty, // 指向 deleteProperty
has: has$1,
ownKeys
};
型参4: 指向一套可变收集行为方法
型参5: 指向了一个
WeakMap
-
判断型参5
reactiveMap
(响应式映射)内是否有我们传入的对象 -
获取传入的引用类型具体是那种?
最后知道了,该引用类型是
Object
然后我们知道了,
Object
、Array
共用type 1
,Map
、Set
、WeakMap
、WeakSet
共用type 2
如果是
type 2
,创建Proxy
传入的属性描述符,是型参4指向的那套,type 1
则使用型参3那套然后记录好响应式映射关系,这个记录响应式映射关系也是有门道的,它是全局的所有响应式的映射,之前有9个映射, 当我们这个对象的依赖关系被记录后,一共有10个映射了
最后返回
Proxy
对象然后我们通过
RefImpl()
构造出来的ref对象内部已经保存了这个proxy
,因此,我们也说vue3的响应式是基于Proxy
的
引用类型ref()的get、set之旅
- 首先返回了
proxy
对象本身 - 我们调用
proxy.info
,proxy
对象拦截到了我们的get('info')
操作 - 经过一系列判断,判断是否只读,是否是浅层响应式,是否是
Array
后走向针对Object
的Reflect.get()
方法 - 我们获取到了
info
对应的普通对象,它现在还不是一个proxy
对象 - 现在我们把
info
对应的这个普通对象传递给reactive()
,要把它也变成一个proxy
对象 - 接下来干的事和之前初始化一个新的引用类型一样
ref()
!全局的weakMap
里又多了一对映射关系!get('info')
也执行完成了! - 现在开始执行
set('age')
,当然,现在是对刚才get('info')
时生成的proxy
对象进行操作,从这里也可以看出,针对Object
、Array
的操作被进一步细化了 Reflect.set()
执行完成后,age
就已经更新了,接下来要做的就是去触发对应的effect
- 判断传入
set()
的key
是否是存在,不存在就执行add
类型的triggter
- 存在就判断这个
key
的值和现在要set
的值是否一样 - 一样就不管,不一样才去触发
set
类型trigger()
- 同时从这里也可以体会到,
proxy
和vue2
的add
的差距 - 并且我们也可以明显看出来,在
Reflect
中是如何针对浅层响应式以及深度响应式的
- 存在就判断这个
- 执行
trigger()
,产生副作用
vue3响应式实现调试小结
响应式创建流程:
ref()
本身可以创建基础类型的响应式对象,也可以创建引用类型的响应式对象- 基础类型 的响应式对象本身跟
proxy
没啥关系 - 引用类型 的响应式对象内部是基于
proxy
对象的,并且,ref()
将引用类型转换为响应式对象本身还是基于reactive()
的,ref()
就是做了一层包装,所以我们本身也探索了reactive()
是实现响应式的 reactive()
进行初始化的时候,它会先去全局的响应式映射关系里找有没有传入的这个对象的proxy
对象,没有才创建新的proxy
对象,最后返回的时候,补全映射关系,所以我们也可以说,所有的引用类型的响应式数据的映射关系是保存在一个全局的weakMap
里Object
、Array
与Map
、Set
、WeakMap
、WeakSet
采用的是不同的Proxy
拦截handle
,就算是同一个拦截handle
,也会根据数据类型的不同,进一步细化操作- 我们如果对引用类型的Ref对象进行了
get
操作,返回结果如果是一个引用类型,那么会对其使用reactive()
,将其也变成一个proxy
对象,并将其记录在全局weakMap
中,这也是vue3
与vue2
的一大区别,vue2
的深度监听需要初始化时一次性递归到底
转载自:https://juejin.cn/post/7276769559587274767