当前位置:首页 > 前端 > Vue > 正文内容

聊聊vue3中ref和reactive的区别与底层实现

virtualman4个月前 (07-24)Vue702

从Vue3源码角度简单说下ref和reactive的关系 - 知乎

 

Vue 3 中引入了新的响应式 API,包括 refreactive,这两个API都用于创建响应式引用,但是它们之间存在一些关键的区别,以及它们在底层的实现机制也有所不同。

一、ref 和 reactive 的区别

0. 一句话区分

ref 用来定义: 基本数据类型的数据 。

reactive 用来定义: 对象(或数组)类型的数据 。 

1. 使用方式

  • ref:

    • ref 创建的是一个带有 .value 属性的对象,用于封装基本类型(如字符串、数字等)或者复杂类型的响应式引用。
    • 当你使用 ref 时,必须通过 .value 访问或修改值。
    • 适用于单值的状态管理。
  • reactive:

    • reactive 直接返回一个对象,这个对象是原始对象的响应式代理。
    • 你可以像操作普通对象一样操作 reactive 创建的对象,不需要额外的属性访问器。
    • 更适合用于对象和数组的状态管理,因为它不需要额外的包装层。

在下面的示例中,单击按钮后 personRef 和 personReactive 都会修改 name 属性,随后便会在 UI 上得到响应,但对普通 JS 对象 person 来说就不会。 

<template>
  {{ person.name }} <!-- 不会变为 张三-->
  {{ personRef.name }} <!-- 会变为 张三-->
  {{ personReactive.name }} <!-- 会变为 张三-->
  <button @click="changeName('张三')">Change Name</button>
</template>

<script setup lang="ts">
  import { ref, reactive } from 'vue'

  const person = { name: 'John' }
  const personRef = ref({ name: 'John' })
  const personReactive = reactive({ name: 'John' })

  const changeName = (name: string) => {
    person.name = name
    personRef.value.name = name
    personReactive.name = name
  }
</script>

2.响应性范围

  • ref:

    • 只有 .value 内部的值是响应式的。
  • reactive:

    • 对象或数组中的所有属性都是响应式的。

二、底层实现

1、Ref 的VUE实现

下面是 Vue.js 3 中 ref() 实现的部分代码。

ref 是通过一个特殊的 RefImpl 类(或类似的实现)来创建的,这个类负责包装原始值,并且通过 Proxy 或其他机制实现对值的响应式追踪。 RefImpl 类实现了 .value 的 getter 和 setter 方法,通过调用 tracktrigger 来处理依赖的收集和触发。而 convertToReactive 函数可能是调用 reactive 或者使用 Proxy 来创建响应式数据结构的函数。 

ref在面对对象数据类型时,仍调用了toReactive函数,把ref转化为了reactive。

//这是一段简化后的VUE实现
function ref(value) {
  return new RefImpl(value);
}

class RefImpl {
  constructor(value) {
    this._rawValue = value; // 原始值
    this._value = convertToReactive(value); // 转换成响应式的
    this.dep = new Dep(); // 依赖收集器
  }

  get value() {
    track(this.dep); // 收集依赖
    return this._value;
  }

  set value(newVal) {
    this._rawValue = newVal;
    this._value = convertToReactive(newVal); // 将新值转换为响应式
    trigger(this.dep); // 触发依赖更新
  }
}

// 假设我们有 convertToReactive 和依赖收集/触发的函数
function convertToReactive(value) {
  // 这里可以是 reactive 或者是更复杂的 Proxy 创建逻辑
  return reactive(value);
}

function track(dep) {
  // 收集依赖的逻辑
}

function trigger(dep) {
  // 触发依赖更新的逻辑
}

2、Reactive的VUE实现

当我们调用 reactive 函数时,会将传入的普通 JavaScript 对象转换为一个 Proxy 对象,通过拦截 get 和 set 操作,实现对整个对象的监听。当读取对象的属性时,会触发 get 操作,返回对应属性的值。当修改对象的属性时,会触发 set 操作,将新的值赋给对应的属性。

核心是利用了 JavaScript 的 Proxy 对象来实现响应式机制,通过定义不同的处理器(mutableHandlers, mutableCollectionHandlers)来捕捉对象属性的读写操作,并通过 WeakMap 缓存已创建的代理对象以提高性能。这种设计允许 Vue 3 能够有效地追踪和响应数据变化,从而实现视图的自动更新。

//从vue源码中截取.
export function reactive<T extends object>(target: T): Reactive<T>
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    return target
  }
//这里调用了 createReactiveObject 函数来创建响应式对象。传入的参数包括目标对象、一个布尔值表示是否只读、基础的 Proxy 处理器、集合类型的处理器以及一个 WeakMap 用来缓存已创建的代理对象。

  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap,
  )
}
/*
此函数的主要作用:
检查目标对象是否为对象类型,如果不是则警告并直接返回。
检查目标对象是否已经是一个代理,如果是并且不是从 reactive 到 readonly 的转换,那么直接返回现有的代理。
检查代理映射表中是否已经有目标对象的代理,如果有则返回已存在的代理,避免重复创建。
检查目标对象是否是可以被观察的类型,如果不可观察直接返回。
根据目标对象的类型选择适当的 Proxy 处理器,对于集合类型(如 Set、Map)使用特定的处理器。
使用 new Proxy 创建一个新的代理对象,其中包含了目标对象和对应的处理器。
将新创建的代理对象添加到代理映射表中,以便后续查找。
最终返回新创建的代理对象。
*/
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>,
) {
  if (!isObject(target)) {
    if (__DEV__) {
      warn(
        `value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(
          target,
        )}`,
      )
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
  )
  proxyMap.set(target, proxy)
  return proxy
}

相关文章

【VUE】深入讨论关于VUE的深拷贝和浅拷贝问题

【VUE】深入讨论关于VUE的深拷贝和浅拷贝问题

一、深拷贝和浅拷贝的定义: 1、深拷贝 :指拷贝对象的具体内容,并且为对象分配新的内存地址。深拷贝结束之后,两个对象虽然存的值是一样的,但是内存地址不一样,互不影响,互不干涉。 2、浅拷贝 :指对内存地址的复制,让目标对象指针和源对象指向同一片内存空间。浅拷贝只会拷贝基本数据类型的值,以及实例对象的...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。