vue3 中 ref 和 reactive 的区别

在 Vue3 中,ref 和 reactive 是实现响应式数据的两个核心 API,均基于 @vue/reactivity 模块,但它们的使用场景、底层实现和特性存在显著差异。以下从核心区别、底层原理、使用场景等方面详细解析。

一、核心区别概览

维度 ref reactive
支持的数据类型 基本类型(number/string/boolean 等)、对象、数组 仅对象或数组(不支持基本类型)
访问方式 通过 .value 访问 / 修改值 直接访问 / 修改属性(无需 .value
响应式实现 对基本类型:包装为带 .value 的对象;对对象:内部转为 reactive 代理 基于 Proxy 直接代理对象 / 数组
解构特性 解构后会丢失响应式(需用 toRefs 处理) 解构后会丢失响应式(需用 toRefs 处理)
适用场景 基本类型数据、单一值(如计数器、开关) 复杂对象 / 数组(如用户信息、列表数据)

二、底层实现原理

1. ref 的实现逻辑

ref 的核心是将数据包装为一个 带 .value 属性的响应式对象,源码简化逻辑如下:

typescript

function ref(value) {
  // 创建一个包含 .value 的对象(RefImpl 实例)
  const refObject = {
    _value: convert(value), // 若 value 是对象,转为 reactive 代理
    get value() {
      track(this, 'get', 'value'); // 依赖收集
      return this._value;
    },
    set value(newVal) {
      newVal = convert(newVal); // 新值为对象时,转为 reactive
      if (hasChanged(newVal, this._value)) {
        this._value = newVal;
        trigger(this, 'set', 'value'); // 触发更新
      }
    }
  };
  return refObject;
}

// 辅助函数:若值为对象,转为 reactive 代理
function convert(value) {
  return isObject(value) ? reactive(value) : value;
}
  • 对基本类型ref(1) 会创建一个 RefImpl 实例,value 的 get/set 被拦截,实现响应式。
  • 对对象 / 数组ref({ name: 'foo' }) 内部会通过 convert 函数将对象转为 reactive 代理(即 ref 包裹的对象本质是 reactive 代理),此时 .value 指向该代理。
2. reactive 的实现逻辑

reactive 基于 ES6 Proxy 直接对对象 / 数组进行代理,源码简化逻辑如下:

typescript

function reactive(target) {
  // 仅代理对象/数组,非对象直接返回
  if (!isObject(target)) return target;

  // 创建 Proxy 代理
  return new Proxy(target, {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver);
      track(target, 'get', key); // 依赖收集
      // 若属性值是对象,递归代理(深层响应式)
      return isObject(result) ? reactive(result) : result;
    },
    set(target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver);
      const result = Reflect.set(target, key, value, receiver);
      if (hasChanged(value, oldValue)) {
        trigger(target, 'set', key); // 触发更新
      }
      return result;
    },
    // 拦截 delete、has 等操作...
  });
}
  • 仅支持对象 / 数组:若传入基本类型(如 reactive(1)),会直接返回原值,不具备响应式。
  • 深层响应式:访问对象的嵌套属性时(如 obj.a.b),会自动对嵌套对象创建 Proxy 代理,确保深层数据变化可被监听。

三、关键特性对比

1. 数据类型支持
  • ref:无类型限制,可接收基本类型(numberstringbooleannullundefined)、对象、数组。例:const count = ref(0); const user = ref({ name: 'vue' });

  • reactive:仅支持对象或数组(包括 MapSet 等复杂对象),传入基本类型时返回原值且无响应式。例:const user = reactive({ name: 'vue' }); // 有效const count = reactive(0); // 无效,count 仍是 0

2. 访问与修改方式
  • ref:必须通过 .value 访问或修改值(模板中使用时会自动解包,无需 .value)。例:

    javascript

    运行

    const count = ref(0);
    console.log(count.value); // 0(脚本中必须用 .value)
    count.value = 1; // 修改值
    
    const user = ref({ name: 'vue' });
    user.value.name = 'vue3'; // 对象属性修改(.value 指向 reactive 代理)
    

    模板中自动解包:<div>{{ count }}</div>(无需写 count.value)。

  • reactive:直接访问或修改属性,无需 .value。例:

    javascript

    运行

    const user = reactive({ name: 'vue' });
    console.log(user.name); // 'vue'
    user.name = 'vue3'; // 直接修改属性
    
3. 解构与响应式丢失
  • ref 和 reactive 共同问题:当对响应式对象解构时,会提取出原始值(非响应式),导致修改解构后的值无法触发更新。例:

    javascript

    运行

    // ref 解构
    const count = ref(0);
    const { value: c } = count; // c 是原始值 0,修改 c 不影响 count
    
    // reactive 解构
    const user = reactive({ name: 'vue' });
    const { name } = user; // name 是原始值 'vue',修改 name 不影响 user
    
  • 解决方案:使用 toRefs 将响应式对象转为 “ref 集合”,确保解构后仍保持响应式:

    javascript

    运行

    import { reactive, toRefs } from 'vue';
    
    const user = reactive({ name: 'vue', age: 3 });
    const { name, age } = toRefs(user); // name 和 age 都是 ref 对象
    
    name.value = 'vue3'; // 修改会触发更新,且同步到 user.name
    
4. 数组与集合的处理
  • ref 处理数组ref 包裹数组时,.value 指向数组的 reactive 代理,可直接通过 .value 操作数组方法(pushsplice 等),响应式有效。例:

    javascript

    运行

    const list = ref([1, 2, 3]);
    list.value.push(4); // 响应式有效,视图会更新
    
  • reactive 处理数组:直接代理数组,可直接调用数组方法,响应式有效。例:

    javascript

    运行

    const list = reactive([1, 2, 3]);
    list.push(4); // 响应式有效
    
  • reactive 处理 Map/Set:支持对 MapSet 的代理,但需注意:修改集合时需使用其原生方法(setdelete 等),直接修改属性(如 map.key = value)无效。例:

    javascript

    运行

    const map = reactive(new Map());
    map.set('key', 'value'); // 有效,响应式
    map.key = 'value'; // 无效,非响应式
    

四、使用场景建议

  1. 优先使用 ref 的场景

    • 存储基本类型数据(如计数器、开关状态、表单输入值)。
    • 单一值的响应式(如 const visible = ref(false))。
    • 不确定数据类型时(ref 兼容性更强)。
  2. 优先使用 reactive 的场景

    • 存储复杂对象或数组(如用户信息 { name, age }、列表数据 [{}, {}])。
    • 希望直接访问属性(避免 .value 繁琐)时。

总结

ref 和 reactive 的核心差异在于 数据类型支持 和 访问方式

  • ref 是 “万能容器”,通过 .value 处理所有类型,适合基本类型和单一值。
  • reactive 是 “对象代理”,直接操作属性,适合复杂对象 / 数组。

两者底层均依赖响应式系统(Proxy 和依赖收集),实际开发中可根据数据类型和使用习惯选择,配合 toRefs 解决解构响应式丢失问题。

转载请说明出处内容投诉
CSS教程网 » vue3 中 ref 和 reactive 的区别

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买