likes
comments
collection
share

我的ref调试之旅

作者站长头像
站长
· 阅读数 15

通过调试,一窥ref()reactive()的区别在哪?为什么vue3不使用Object.defineProperty()实现响应式了?

基础类型初始、set、get流程

ref()初始赋值之旅,它是如何把一个基础类型变为响应式的

  1. 首先我再ref处断住,接下来让我们一窥究竟! 我的ref调试之旅

  2. 实际ref内部调用了createRef,而createRef接收两个值,第一个形参rawValue是我们传进来的hello,第二个形参shallow是是否浅层监听,ref内部明显帮我们传了false,令其深度监听!createRef内部调用了isRef,isRef在官网上就有说明,用来判断一个对象是否是Ref对象,createRef做的是,是Ref对象你就回去,不是我就把你变成我的的人! 我的ref调试之旅

  3. isRef原来是这么判断是否是Ref对象的,每个Ref对象在出身的时候应该都背上了一个.__v_isRef胎记,我们调试完ref的实现本身应该就能看到它本身诞生的过程! 我的ref调试之旅

  4. 现在我们的isRef检测不通过,我们有一句话是这么说的,你是不是直男?不是?不是我们就把你掰直!接下来就需要进入RefImpl这个class 我的ref调试之旅

  5. RefImpl帮我们构造一个ref对象 我的ref调试之旅

{
  __v_isShallow: false, // 是否浅层监听
  dep: undefined, // 咱也不知道是啥,猜测是深度
  __v_isRef: true, // 打上了ref对象胎记
  _rawValue: "hello vue", //原本的值
}

5-1. toRaw干了什么?它会一致递归寻找最初传进ref()value直到.__v_rawundefined,那就是找到了非ref()包裹时最原始的值,当然.__v_raw只针对引用类型才生效,针对原始值不生效 我的ref调试之旅

5-2. toReactice()干了什么?先判断是否是引用类型,如果是就去调用reactive()方法去转换,如果不是引用类型,返回传入值,传入值是什么?就是我们调用ref()传的那玩意 我的ref调试之旅

我的ref调试之旅

  1. 至此,我们的ref()初始化完成了 我的ref调试之旅

我们直到了官网那些平时看起来八竿子都打不到的api:isReftoRaw干了什么,同时也获得了一个重要的信息!就算是使用ref()创建响应式数据,传入的如果是引用类型,也要去调用reactive()

基础类型ref对象set之旅

  1. 继续回到RefImpl这个class,这次探索set value(newVal)方法 1-1. 先通过isShallow()isReadonly判断是否浅层监听或者是否是只读,这也是为什么shallowRef无法监听到深层改变的原因 1-2. 如果是的话,还是直接使用newVal 1-3. 如果不是就通过toRaw()获取响应式前的值,因为传入的是stringtoRaw()直接返回string 我的ref调试之旅

  2. 接着进入hasChanged()里面对比Ref对象的初始值是否有变化,因为原始值变更为另一个原始值,内存地址变更了,所以返回true 我的ref调试之旅

  3. 接下来的操作跟之前初始化时一样,记录下响应式化之前的值,以及响应式后的值 我的ref调试之旅

  4. 进入triggerRefValue(),它接收两个参数,一个是现在的ref对象,一个是最新传入的值。由于我们现在还未渲染也没有别的地方依赖该ref对象,所以不会触发任何的effect(副作用)就是进来逛了下街,但是现在也可以看出来dep是干嘛的了,70%的概率就是监听的深度,决定后续递归深度(猜测) 我的ref调试之旅

  5. 至此我们的原始值的ref对象的set就执行完了 我的ref调试之旅

基础类型ref对象get之旅

我的ref调试之旅

  1. get简单粗暴,内部只是去执行trackRefValue() 我的ref调试之旅

  2. 由于trackRefValue()内部的一个自由变量shouldTrackfalse,所以本质还是进来逛了圈街 我的ref调试之旅

  3. 最后灰溜溜的带上当前的value返回了 我的ref调试之旅 我的ref调试之旅

引用类型ref()初始、set、get

引用类型ref()的初始化之旅

  1. 前面与基础类型初始化一致,在这toReactive()走向另一条路,进入reactive() 我的ref调试之旅

  2. reactive()内部调用createReacticeObject()去创建引用类型的响应式对象! 我的ref调试之旅 形参1:我们传入的对象;形参2:是否只读 我的ref调试之旅 型参3:指向一套引用类型ref对象内部使用的getsetdelete的公共方法 我的ref调试之旅

const mutableHandlers = {
  get: get$1, // 指向 createGetter
  set: set$1, // 指向 createSetter
  deleteProperty, // 指向 deleteProperty
  has: has$1,
  ownKeys
};

型参4: 指向一套可变收集行为方法 我的ref调试之旅 型参5: 指向了一个WeakMap 我的ref调试之旅

  1. 判断型参5 reactiveMap(响应式映射)内是否有我们传入的对象 我的ref调试之旅

  2. 获取传入的引用类型具体是那种? 我的ref调试之旅 最后知道了,该引用类型是Object 我的ref调试之旅 然后我们知道了,ObjectArray共用type 1, MapSetWeakMapWeakSet共用type 2 我的ref调试之旅 如果是type 2,创建Proxy传入的属性描述符,是型参4指向的那套,type 1则使用型参3那套 我的ref调试之旅 然后记录好响应式映射关系,这个记录响应式映射关系也是有门道的,它是全局的所有响应式的映射,之前有9个映射, 当我们这个对象的依赖关系被记录后,一共有10个映射了 我的ref调试之旅 最后返回Proxy对象 我的ref调试之旅 然后我们通过RefImpl()构造出来的ref对象内部已经保存了这个proxy,因此,我们也说vue3的响应式是基于Proxy我的ref调试之旅

我的ref调试之旅

引用类型ref()的get、set之旅

我的ref调试之旅

  1. 首先返回了proxy对象本身 我的ref调试之旅
  2. 我们调用proxy.infoproxy对象拦截到了我们的get('info')操作 我的ref调试之旅
  3. 经过一系列判断,判断是否只读,是否是浅层响应式,是否是Array后走向针对ObjectReflect.get()方法 我的ref调试之旅
  4. 我们获取到了info对应的普通对象,它现在还不是一个proxy对象 我的ref调试之旅
  5. 现在我们把info对应的这个普通对象传递给reactive(),要把它也变成一个proxy对象 我的ref调试之旅
  6. 接下来干的事和之前初始化一个新的引用类型一样ref()!全局的weakMap里又多了一对映射关系!get('info')也执行完成了! 我的ref调试之旅
  7. 现在开始执行set('age'),当然,现在是对刚才get('info')时生成的proxy对象进行操作,从这里也可以看出,针对ObjectArray的操作被进一步细化了 我的ref调试之旅
  8. Reflect.set()执行完成后,age就已经更新了,接下来要做的就是去触发对应的effect 我的ref调试之旅
  9. 判断传入set()key是否是存在,不存在就执行add类型的triggter
    1. 存在就判断这个key的值和现在要set的值是否一样
    2. 一样就不管,不一样才去触发set类型trigger()
    3. 同时从这里也可以体会到,proxyvue2add的差距
    4. 并且我们也可以明显看出来,在Reflect中是如何针对浅层响应式以及深度响应式我的ref调试之旅
  10. 执行trigger(),产生副作用

vue3响应式实现调试小结

响应式创建流程: 我的ref调试之旅

  1. ref()本身可以创建基础类型的响应式对象,也可以创建引用类型的响应式对象
  2. 基础类型 的响应式对象本身跟proxy没啥关系
  3. 引用类型 的响应式对象内部是基于proxy对象的,并且,ref()将引用类型转换为响应式对象本身还是基于reactive()的,ref()就是做了一层包装,所以我们本身也探索了reactive()是实现响应式的
  4. reactive()进行初始化的时候,它会先去全局的响应式映射关系里找有没有传入的这个对象的proxy对象,没有才创建新的proxy对象,最后返回的时候,补全映射关系,所以我们也可以说,所有的引用类型的响应式数据的映射关系是保存在一个全局的weakMap
  5. ObjectArrayMapSetWeakMapWeakSet采用的是不同的Proxy拦截handle,就算是同一个拦截handle,也会根据数据类型的不同,进一步细化操作
  6. 我们如果对引用类型的Ref对象进行了get操作,返回结果如果是一个引用类型,那么会对其使用reactive(),将其也变成一个proxy对象,并将其记录在全局weakMap中,这也是vue3vue2的一大区别,vue2的深度监听需要初始化时一次性递归到底