5.vue3 ref 和 reactive 我该用哪个,解析 ref 与 reactive原理和区别

在 Vue3 中,ref 和 reactive 是 Composition API 的核心工具,用于创建响应式数据。尽管它们功能类似,但在使用场景、实现原理和实际应用中存在显著差异。本文将从面试和工作中的常见问题入手,深入探讨两者的区别、实现机制以及项目中的最佳实践,助你在 Vue3 开发中游刃有余。

一、核心差异对比

1.1 使用上的基本区别

  • ref:适合定义任何类型的数据,包括基本类型(如数字、字符串)和引用类型(如对象、数组)。访问和修改数据时需通过 .value。
  • reactive:仅适用于对象类型(对象或数组),不支持基本类型。直接操作对象的属性即可触发响应式更新。
// 基本类型
const count = ref(0);      // ✅ 推荐
count.value = 1;           // 修改值

const num = reactive(0);   // ❌ 抛出警告,非对象类型无效

// 引用类型
const obj1 = reactive({ a: 1 });  // ✅ 直接操作属性
obj1.a = 2;

const obj2 = ref({ a: 1 });       // ✅ 通过 .value 操作
obj2.value.a = 2;

面试注意:如果简单回答“ref 用于基本类型,reactive 用于对象”,虽然不完全错,但不够全面。实际选择应基于赋值方式和使用场景。

1.2 赋值行为的差异

两者的关键区别在于直接赋值时的表现:

  • reactive:直接赋值会破坏响应式。
  • ref:通过 .value 赋值保持响应式。
// reactive 错误示例
let obj = reactive({});
setTimeout(() => {
  obj = { a: 123 };       // ❌ 失去响应式,变为普通对象
  console.log(obj);       // { a: 123 }
}, 1000);

// ref 正确示例
const state = ref({});
setTimeout(() => {
  state.value = { a: 123 }; // ✅ 保持响应式
  console.log(state.value); // Proxy { a: 123 }
}, 1000);

工作注意:在请求数据时,通常会直接赋值(如 data = res),此时推荐使用 ref,避免 reactive 的响应式丢失问题。

1.3 修改数据的对比

对于对象属性的修改,两者都能保持响应式,但操作方式不同:

let obj1 = reactive({ a: 1 });
let obj2 = ref({ a: 1 });

setTimeout(() => {
  obj1.a = 100;         // ✅ 直接修改属性
  obj2.value.a = 100;   // ✅ 通过 .value 修改属性
}, 1000);

区别:ref 多了 .value 的访问层,而 reactive 更直观。


二、ref 的实现原理

2.1 RefImpl 核心代码

ref 的实现依赖于 RefImpl 类,以下是简化的实现:

class RefImpl {
  constructor(value) {
    this._rawValue = value; // 原始值
    this._value = isObject(value) ? reactive(value) : value; // 对象转为 reactive
    this.dep = new Set(); // 依赖收集容器
  }

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

  set value(newVal) {
    if (hasChanged(newVal, this._rawValue)) { // 值变化时更新
      this._rawValue = newVal;
      this._value = isObject(newVal) ? reactive(newVal) : newVal;
      triggerRefValue(this); // 触发更新
    }
  }
}

function ref(value) {
  return new RefImpl(value);
}

实现步骤

  1. 判断值是否为对象,若是则用 reactive 包装。
  2. 将值存储为 _value 属性。
  3. 通过 get 和 set 定义 value 属性,实现依赖收集和更新触发。
  4. 返回实例对象。

注意:shallowRef 不会将对象包装为 reactive,仅保持浅层响应式。

2.2 响应式触发机制

  • ref 的 get 和 set
    • get value:访问 .value 时触发,收集依赖。
    • set value:直接修改 .value(如 ref.value = xxx)时触发,仅在整体替换时生效。
  • Proxy 的 get 和 set
    • 如果 ref 的值是对象,内部通过 reactive 转为 Proxy。
    • 修改对象属性(如 ref.value.a = 1)触发 Proxy 的 set。
const obj = ref({ a: 1 });
obj.value.a = 2;      // 触发 Proxy 的 set
obj.value = { b: 3 }; // 触发 RefImpl 的 set

三、常见现象与问题

3.1 为什么 ref 需要 .value?

  • 直接赋值(如 ref = 123)会覆盖整个 RefImpl 实例,导致响应式丢失。
  • .value 是 RefImpl 的属性,确保操作的是内部值。
const count = ref(0);
count = 1;        // ❌ 失去响应式
count.value = 1;  // ✅ 正确

3.2 ref 的对象是响应式的

ref 的值如果是对象,会自动转为 reactive,因此对象属性修改也能触发更新:

const obj = ref({ a: 1 });
obj.value.a = 2; // ✅ 触发响应式

浅层 ref 例外:shallowRef 不包装对象,属性修改无响应式。


四、是否只用 ref,不用 reactive?

4.1 可以只用 ref 吗?

理论上可以,因为:

  • ref 支持所有类型。
  • 对象类型内部会转为 reactive,功能覆盖 reactive。
const state = ref({ a: 1 });
state.value.a = 2; // 等价于 reactive

严格来说,没有绝对只能用 reactive 的场景。但在以下情况更适合用 reactive:

  • 深层对象操作:直接操作多层属性时,reactive 更简洁。
  • 避免 .value:团队规范或代码风格偏好时。
const state = reactive({ user: { name: 'Alice' } });
state.user.name = 'Bob'; // 简洁

4.3 项目中常用哪个?

  • 简单数据:ref,如计数器、状态标志。
  • 复杂对象:reactive,如表单数据、嵌套结构。
  • 实际建议:根据赋值习惯选择,请求数据用 ref,内部修改用 reactive。

五、Proxy 的原理简解

reactive 基于 ES6 的 Proxy,拦截对象操作:

const reactiveHandler = {
  get(target, key) {
    track(target, key); // 依赖收集
    return Reflect.get(target, key);
  },
  set(target, key, value) {
    const oldValue = target[key];
    Reflect.set(target, key, value);
    if (hasChanged(value, oldValue)) {
      trigger(target, key); // 触发更新
    }
    return true;
  }
};

function reactive(obj) {
  return new Proxy(obj, reactiveHandler);
}
  • get:访问属性时收集依赖。
  • set:修改属性时触发更新。
  • 优势:无需像 Vue2 的 Object.defineProperty 手动定义每个属性。

六、复杂情况解析

6.1 给 ref 赋值 reactive

const data = reactive({ a: 1 });
const state = ref(data);
state.value.a = 2; // ✅ 保持响应式

注意:即使是 shallowRef,赋值 reactive 后仍保持响应式,shallow 无效。

6.2 给 reactive 赋值 ref

const count = ref(0);
const state = reactive({ num: count });
state.num.value = 1; // ✅ 仍为 ref 对象

用途:可用此方法让 reactive 包含基本类型。


七、企业级最佳实践

7.1 使用规范

  • 基本类型:统一用 ref。
  • 复杂对象:优先用 reactive。
  • 组件 props:用 ref 风格。
  • 全局状态:结合 Pinia 使用 reactive。

7.2 性能优化

  • 大对象:用 shallowRef 减少开销。
  • 高频更新:用 computed 缓存。
const bigData = shallowRef(largeObj);
bigData.value = { ...bigData.value, key: 'new' };

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值