Vue3 的 ref 与 reactive 区别与实现原理
字数 2093 2025-11-04 08:34:40

Vue3 的 ref 与 reactive 区别与实现原理

题目描述
Vue3 的响应式系统提供了 ref 和 reactive 两个核心 API 来创建响应式数据。理解它们的区别、适用场景和底层实现原理,对于正确使用 Vue3 和应对面试至关重要。

知识讲解

第一步:设计初衷与基本用法

  1. reactive

    • 设计目标:用于创建一个深度响应式对象或数组。它通过 Proxy 代理整个对象,递归地将对象的所有属性都转换为响应式的。
    • 基本用法
      import { reactive } from 'vue';
      
      const state = reactive({
        count: 0,
        user: {
          name: 'Alice'
        }
      });
      
      // 修改属性,视图会自动更新
      state.count++;
      state.user.name = 'Bob'; // 嵌套属性也是响应式的
      
  2. ref

    • 设计目标:用于创建一个可以包装任意值(包括基本类型,如 string, number, boolean,以及对象和数组)的响应式引用。它解决的是 reactive 无法直接对基本类型进行代理的问题。
    • 基本用法
      import { ref } from 'vue';
      
      const count = ref(0); // 包装一个数字
      const message = ref('Hello'); // 包装一个字符串
      const obj = ref({ name: 'Alice' }); // 包装一个对象
      
      // 访问和修改需要通过 .value 属性
      console.log(count.value); // 0
      count.value++; // 修改
      message.value = 'World';
      obj.value.name = 'Bob'; // 注意,这里访问的是 .value 上的属性
      
    • 模板中的自动解包:在模板中,你不需要写 .value,Vue 会自动帮你解包。
      <template>
        <div>{{ count }}</div> <!-- 无需写成 count.value -->
      </template>
      

第二步:核心区别对比

特性 ref reactive
数据类型 可以包装任何值(基本类型和引用类型) 只能用于对象类型(Object, Array, Map, Set)
访问/修改 需要通过 .value 属性 可以直接访问和修改属性
模板使用 自动解包,无需 .value 直接访问属性
TS 类型定义 Ref<T> 对象本身就是响应式的,类型更直观
实现原理 通过一个对象的 .value 属性的 getter/setter 实现 通过 Proxy 代理整个对象

第三步:底层实现原理剖析

  1. reactive 的实现核心

    • 技术:基于 ES6 的 Proxy
    • 过程
      • reactive(obj) 会返回 obj 的 Proxy 代理。
      • Getter 追踪:当你读取代理对象的某个属性(如 state.count)时,Proxy 的 get 陷阱函数会触发。Vue 会在这里执行 track(target, key),将当前正在运行的副作用函数(例如,组件的渲染函数)"收集"起来,建立依赖关系。这叫做依赖收集
      • Setter 触发更新:当你修改代理对象的属性(如 state.count = 1)时,Proxy 的 set 陷阱函数会触发。Vue 会在这里执行 trigger(target, key),去找到所有依赖于这个属性的副作用函数,并重新执行它们。这叫做触发更新
    • 递归响应:在 get 陷阱中,如果读取到的属性值本身也是一个对象,Vue 会递归地对其调用 reactive(),确保嵌套属性也是响应式的。
  2. ref 的实现核心

    • 技术:基于普通对象的属性访问器(getter/setter)
    • 过程
      • ref(value) 会返回一个被称为 "ref 对象" 的普通对象,其结构为 { value: ... }
      • 这个 ref 对象有一个私有的属性 _value 用来存储实际的值。
      • 它对外暴露了一个公共的 value 属性。对这个 value 属性的读取和修改,被 getter 和 setter 拦截。
      • Getter 追踪:当你读取 myRef.value 时,getter 函数被调用。它内部会执行 track(ref, 'value'),追踪对 'value' 这个 key 的访问。
      • Setter 触发更新:当你设置 myRef.value = newValue 时,setter 函数被调用。它内部会先更新 _value,然后执行 trigger(ref, 'value'),通知所有依赖进行更新。
    • ref 的优化:如果传入 ref 的是一个对象,Vue 内部会自动调用 reactive 方法来深度处理这个对象。所以 ref({}) 在响应性方面等价于 reactive({}),只是访问方式不同(需要通过 .value)。

第四步:如何选择与总结

  • 使用 ref 的场景

    • 定义基本类型的响应式数据(如字符串、数字、布尔值)。
    • 定义未来值不确定的响应式数据。因为 ref 更通用。
    • 在逻辑复用(Composables)中返回响应式数据时,ref 更容易被解构而保持响应性。
  • 使用 reactive 的场景

    • 定义一组逻辑上相关联的数据,比如一个表单对象、一个页面的状态管理对象。这样你不需要在每个属性前加 .value,代码更简洁。
  • 核心记忆点

    • reactive 的局限性是只对对象类型有效,且使用 Proxy。
    • ref 的普适性是因为它用 getter/setter 包装了 .value 属性,从而可以容纳任何值。
    • 它们最终都汇入了 Vue3 统一的响应式系统(tracktrigger)。
Vue3 的 ref 与 reactive 区别与实现原理 题目描述 Vue3 的响应式系统提供了 ref 和 reactive 两个核心 API 来创建响应式数据。理解它们的区别、适用场景和底层实现原理,对于正确使用 Vue3 和应对面试至关重要。 知识讲解 第一步:设计初衷与基本用法 reactive 设计目标 :用于创建一个 深度响应式 的 对象或数组 。它通过 Proxy 代理整个对象,递归地将对象的所有属性都转换为响应式的。 基本用法 : ref 设计目标 :用于创建一个可以包装 任意值 (包括基本类型,如 string, number, boolean,以及对象和数组)的响应式引用。它解决的是 reactive 无法直接对基本类型进行代理的问题。 基本用法 : 模板中的自动解包 :在模板中,你不需要写 .value ,Vue 会自动帮你解包。 第二步:核心区别对比 | 特性 | ref | reactive | | :--- | :--- | :--- | | 数据类型 | 可以包装 任何值 (基本类型和引用类型) | 只能用于 对象类型 (Object, Array, Map, Set) | | 访问/修改 | 需要通过 .value 属性 | 可以直接访问和修改属性 | | 模板使用 | 自动解包 ,无需 .value | 直接访问属性 | | TS 类型定义 | Ref<T> | 对象本身就是响应式的,类型更直观 | | 实现原理 | 通过一个对象的 .value 属性的 getter/setter 实现 | 通过 Proxy 代理整个对象 | 第三步:底层实现原理剖析 reactive 的实现核心 技术 :基于 ES6 的 Proxy 。 过程 : reactive(obj) 会返回 obj 的 Proxy 代理。 Getter 追踪 :当你读取代理对象的某个属性(如 state.count )时,Proxy 的 get 陷阱函数会触发。Vue 会在这里执行 track(target, key) ,将当前正在运行的副作用函数(例如,组件的渲染函数)"收集"起来,建立依赖关系。这叫做 依赖收集 。 Setter 触发更新 :当你修改代理对象的属性(如 state.count = 1 )时,Proxy 的 set 陷阱函数会触发。Vue 会在这里执行 trigger(target, key) ,去找到所有依赖于这个属性的副作用函数,并重新执行它们。这叫做 触发更新 。 递归响应 :在 get 陷阱中,如果读取到的属性值本身也是一个对象,Vue 会递归地对其调用 reactive() ,确保嵌套属性也是响应式的。 ref 的实现核心 技术 :基于普通对象的 属性访问器(getter/setter) 。 过程 : ref(value) 会返回一个被称为 "ref 对象" 的普通对象,其结构为 { value: ... } 。 这个 ref 对象有一个私有的属性 _value 用来存储实际的值。 它对外暴露了一个公共的 value 属性。对这个 value 属性的读取和修改,被 getter 和 setter 拦截。 Getter 追踪 :当你读取 myRef.value 时,getter 函数被调用。它内部会执行 track(ref, 'value') ,追踪对 'value' 这个 key 的访问。 Setter 触发更新 :当你设置 myRef.value = newValue 时,setter 函数被调用。它内部会先更新 _value ,然后执行 trigger(ref, 'value') ,通知所有依赖进行更新。 ref 的优化 :如果传入 ref 的是一个对象,Vue 内部会自动调用 reactive 方法来深度处理这个对象。所以 ref({}) 在响应性方面等价于 reactive({}) ,只是访问方式不同(需要通过 .value )。 第四步:如何选择与总结 使用 ref 的场景 : 定义 基本类型 的响应式数据(如字符串、数字、布尔值)。 定义未来值不确定的响应式数据。因为 ref 更通用。 在逻辑复用(Composables)中返回响应式数据时, ref 更容易被解构而保持响应性。 使用 reactive 的场景 : 定义 一组逻辑上相关联的 数据,比如一个表单对象、一个页面的状态管理对象。这样你不需要在每个属性前加 .value ,代码更简洁。 核心记忆点 : reactive 的局限性是只对对象类型有效,且使用 Proxy。 ref 的普适性是因为它用 getter/setter 包装了 .value 属性,从而可以容纳任何值。 它们最终都汇入了 Vue3 统一的响应式系统( track 和 trigger )。