Vue3之从源码的角度分析ref和reactive的区别

我们虽然走的很慢,但从未停止过脚步。

我们经常会使用到 ref 和 reactive,那么他们在底层的实现上有什么区别和联系呢?在说这个问题之前,让我们先看一下 ref 和 reactive 的对比。

ref 和 reactive 对比

  • ref 可以定义基本数据类型和引用数据类型,reactive 只能定义引动数据类型。
  • 在 script 标签中,ref 取值需要通过 .value 的形式。
  • 对 reactive 重新赋值会导致其失去响应性。并且传给一个函数参数时也会失去响应性。

注意:对 ref 和 reactive 进行解构都会导致其失去响应式

由于 ref 源码中用到了 reactive 函数,让我们先看一下 reactive 的源码。

reactive 源码

目录:core-main/packages/reactivity/reactive.ts

先看一下 reactive 函数

   export function reactive(target: object) {
      // if trying to observe a readonly proxy, return the readonly version.
      if (isReadonly(target)) {
        return target
      }
      return createReactiveObject(
        target,
        false,
        mutableHandlers,
        mutableCollectionHandlers,
        reactiveMap,
      )
    }

在该函数中主要调用了 createReactiveObject 方法:

   function createReactiveObject(
      target: Target,
      isReadonly: boolean,
      baseHandlers: ProxyHandler<any>,
      collectionHandlers: ProxyHandler<any>,
      proxyMap: WeakMap<Target, any>,
    ) {
      ......
      const proxy = new Proxy(
        target,
        targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
      )
      proxyMap.set(target, proxy)
      return proxy
    }

而在 createReactiveObject 函数中主要使用了 ES6 新增的 Proxy 来实现响应式,其使用方法如下:

   const proxy = new Proxy(target, {
       set(target, prop, receiver) {
       
       },
       get(target, prop, value, receiver) {
       
       },
       ......
   })

Proxy 为代理模式,第一个参数是所要代理的对象,第二个参数是一个对象,可以在这个对象里写 set, get 函数监听对象的属性操作。所以在 createReactiveObject 函数中主要就是返回了一个 Proxy 的代理对象,而 collectionHandlers 和 baseHandlers 就是对所要代理的对象的配置对象。而在这个对象中在 set 时去 trigger 去收集依赖,而在 get 时 track 去触发依赖的更新。这就是 reactive 的一个基本实现过程。

需要注意的是,在进行依赖的收集时,使用的时 WeakMap,它和 Map 的区别为 Map 是强引用而 WeakMap 为弱引用,当数据不在被使用的时候,WeakMap 会对其进行清除,从而防止内存泄漏。

ref 源码

目录:core-main/packages/reactivity/ref.ts

ref函数:

   export function ref(value?: unknown) {
      return createRef(value, false)
   }

在 ref 函数中调用了 createRef ,那,让我们看看 createRef 函数。

   function createRef(rawValue: unknown, shallow: boolean) {
      if (isRef(rawValue)) {
        return rawValue
      }
      return new RefImpl(rawValue, shallow)
    }

在 createRef 函数中,先进行了一次判断,如果所传入的是 ref 对象,那么直接返回,如果不是,则返回一个 RefImpl 类。RefImpl 类的具体实现如下:

   class RefImpl<T> {
      private _value: T
      private _rawValue: T

      public dep?: Dep = undefined
      public readonly __v_isRef = true

      constructor(
        value: T,
        public readonly __v_isShallow: boolean,
      ) {
        this._rawValue = __v_isShallow ? value : toRaw(value)
        this._value = __v_isShallow ? value : toReactive(value)
      }

      get value() {
        trackRefValue(this)
        return this._value
      }

      set value(newVal) {
        const useDirectValue =
          this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
        newVal = useDirectValue ? newVal : toRaw(newVal)
        if (hasChanged(newVal, this._rawValue)) {
          this._rawValue = newVal
          this._value = useDirectValue ? newVal : toReactive(newVal)
          triggerRefValue(this, DirtyLevels.Dirty, newVal)
        }
      }
    }

让我们看一下构造函数中的 this._value = __v_isShallow ? value : toReactive(value) 代码,如果 __v_isShallow 为 fasle 则会执行 toReactive 函数(__v_isShallow主要是实现浅层响应式的 –> shallowRef), toReactive 函数如下:

   export const toReactive = <T extends unknown>(value: T): T 
   => isObject(value) ? reactive(value) : value

在该函数中使用 isObject 进行判断,如果它是一个对象,则直接调用 reactive 函数,否则原值返回进行进一步的操作。

所以在 ref 的底层当遇到对象时,也会直接调用 reactive 函数,那么在定义对象时使用 reactive 的性能肯定是比 ref 高的。

结语

那为什么使用 reactive 赋值会失去响应性呢?这是因为当用一个对象直接对其赋值时,它并没有使用 Proxy代理 对所赋值的对象进行依赖的收集和更新,自然也就失去了响应性。其他使用 reactive 导致失去相应性的场景也是如此。要想使其获得响应性,可以使用 toRef 和 toRefs(主要应用场景为解构赋值)。

ref 必须使用 .value 获取值的原因:对于简单类型而言,无法使用 Proxy(传入的第一个参数为对象),所以 Vue 对其封装为一个具有 value 属性对象,便于对其进行依赖的收集和更新。

原文链接:https://juejin.cn/post/7350880189836820516 作者:送你颗糖呀

(0)
上一篇 2024年4月2日 下午4:08
下一篇 2024年4月2日 下午4:18

相关推荐

发表回复

登录后才能评论