Vue3 中 ref 与 reactive 的深度解析与对比

在 Vue3 的组合式 API 中,refreactive是实现响应式数据的核心 API。虽然二者都用于创建响应式对象,但设计初衷、使用场景和内部实现存在显著差异。本文将从原理、用法、差异及最佳实践等方面深入剖析这两个 API。

一、基本概念与原理

1. ref

ref是 Vue3 中用于创建基本类型响应式数据 的 API,本质是对基本类型的包装(wrapper),通过一个包含value属性的对象实现响应式。

实现原理

  • 使用RefImpl类包装数据,访问value时触发track(依赖收集),修改value时触发trigger(依赖更新)
  • 对于对象类型,内部会自动调用reactive进行深层响应式转换

typescript

运行

复制代码
// 简化实现
class RefImpl<T> {
  private _value: T;
  constructor(value: T) {
    this._value = isObject(value) ? reactive(value) : value;
  }
  
  get value() {
    track(this, 'value');
    return this._value;
  }
  
  set value(newVal) {
    this._value = isObject(newVal) ? reactive(newVal) : newVal;
    trigger(this, 'value');
  }
}

2. reactive

reactive用于创建对象类型响应式数据 ,基于 ES6 的Proxy实现,支持深层响应式。

实现原理

  • 通过Proxy代理对象的所有属性操作
  • 对对象的get操作进行依赖收集,set操作触发更新
  • 嵌套对象会被递归转换为Proxy实例

typescript

运行

复制代码
// 简化实现
function reactive<T extends object>(target: T): T {
  return new Proxy(target, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver);
      track(target, key);
      return isObject(res) ? reactive(res) : res;
    },
    set(target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver);
      const result = Reflect.set(target, key, value, receiver);
      if (oldValue !== value) {
        trigger(target, key);
      }
      return result;
    }
  });
}

二、基本用法对比

1. ref 的使用

javascript

运行

复制代码
import { ref } from 'vue';

// 基本类型响应式
const count = ref(0);
console.log(count.value); // 0
count.value++; // 触发更新

// 对象类型(内部转为reactive)
const user = ref({ name: '张三', age: 20 });
user.value.name = '李四'; // 响应式更新

// 在模板中使用(自动解包)
// <template>{{ count }} {{ user.name }}</template>

2. reactive 的使用

javascript

运行

复制代码
import { reactive } from 'vue';

// 对象响应式
const state = reactive({
  count: 0,
  user: {
    name: '张三',
    address: {
      city: '北京'
    }
  }
});

state.count++; // 响应式更新
state.user.address.city = '上海'; // 深层响应式

// 数组响应式
const list = reactive([1, 2, 3]);
list.push(4); // 响应式更新

三、核心差异对比

特性 ref reactive
支持类型 基本类型 + 对象类型 仅对象类型(对象 / 数组 / Map/Set 等)
访问方式 需要.value(模板中自动解包) 直接访问属性
深层响应式 自动实现(对象类型) 自动实现
重新赋值 支持(ref.value = 新值 不支持(会丢失响应式)
解构响应性 解构后.value仍保持响应式 直接解构会丢失响应式(需用 toRefs)
使用场景 基本类型、需要重新赋值的场景 复杂对象、不需要整体替换的场景

关键差异详解:

1. 重新赋值行为

javascript

运行

复制代码
// ref支持重新赋值
const num = ref(0);
num.value = 10; // 保持响应式

// reactive不支持整体替换
const obj = reactive({ count: 0 });
obj = { count: 10 }; // 丢失响应式!
2. 解构响应性

javascript

运行

复制代码
// ref解构
const { count } = { count: ref(0) };
count.value++; // 仍保持响应式

// reactive解构
const state = reactive({ count: 0 });
const { count } = state;
count++; // 非响应式!

// 正确做法:使用toRefs
import { toRefs } from 'vue';
const state = reactive({ count: 0 });
const { count } = toRefs(state);
count.value++; // 响应式
3. 模板中的表现

html

预览

复制代码
<!-- ref自动解包 -->
<template>
  <div>{{ count }}</div> <!-- 无需.value -->
  <div>{{ user.name }}</div> <!-- 对象类型也自动解包 -->
</template>

<!-- reactive直接访问 -->
<template>
  <div>{{ state.count }}</div>
</template>

四、使用场景选择

优先使用 ref 的场景:

  1. 基本类型响应式:数字、字符串、布尔值等
  2. 需要重新赋值的对象 :如const form = ref({})需要整体替换
  3. 组合函数返回值:便于解构使用
  4. DOM 元素引用const el = ref(null)

优先使用 reactive 的场景:

  1. 复杂对象 / 状态管理:如表单数据、业务状态
  2. 不需要整体替换的对象:如组件内部的状态对象
  3. 数组操作const list = reactive([])

组合使用示例:

javascript

运行

复制代码
import { ref, reactive } from 'vue';

export default {
  setup() {
    // 基本类型用ref
    const userId = ref(1);
    const loading = ref(false);
    
    // 复杂状态用reactive
    const userInfo = reactive({
      name: '',
      age: 0,
      address: {
        city: '',
        street: ''
      }
    });
    
    // 数组用reactive
    const hobbies = reactive([]);
    
    return {
      userId,
      loading,
      userInfo,
      hobbies
    };
  }
};

五、常见误区与最佳实践

误区 1:过度使用 reactive

javascript

运行

复制代码
// 不推荐
const count = reactive({ value: 0 });
count.value++;

// 推荐
const count = ref(0);
count.value++;

误区 2:解构 reactive 对象丢失响应性

javascript

运行

复制代码
// 错误
const state = reactive({ count: 0 });
const { count } = state;

// 正确
import { toRefs } from 'vue';
const state = reactive({ count: 0 });
const { count } = toRefs(state);

误区 3:ref 对象嵌套 reactive

javascript

运行

复制代码
// 不推荐(冗余)
const user = ref(reactive({ name: '张三' }));

// 推荐
const user = ref({ name: '张三' });
// 或
const user = reactive({ name: '张三' });

最佳实践:

  1. 保持一致性:团队约定统一的使用规范
  2. 按需选择:根据数据类型和使用场景选择合适的 API
  3. 避免嵌套过深:复杂状态可拆分多个响应式对象
  4. 配合 toRefs :组合函数返回 reactive 对象时使用toRefs

六、底层实现差异

ref 的内部结构:

javascript

运行

复制代码
// ref对象结构
const count = ref(0);
console.log(count);
// {
//   __v_isRef: true,
//   value: 0
// }

reactive 的内部结构:

javascript

运行

复制代码
// reactive返回Proxy实例
const state = reactive({ count: 0 });
console.log(state); // Proxy对象
console.log(state.__v_isReactive); // true

响应式判定:

javascript

运行

复制代码
import { isRef, isReactive } from 'vue';

const count = ref(0);
const state = reactive({});

console.log(isRef(count)); // true
console.log(isReactive(state)); // true

总结

refreactive作为 Vue3 响应式系统的基石,各自有明确的设计目标和适用场景:

  • ref:更灵活,支持基本类型和对象,适合独立值和需要重新赋值的场景
  • reactive:更适合复杂对象和集合类型,提供更自然的访问方式

理解二者的差异和适用场景,能帮助开发者更高效地构建 Vue3 应用,避免常见陷阱,编写出更清晰、可维护的响应式代码。

相关推荐
拾忆,想起43 分钟前
Dubbo异步调用与主线程同步完全指南:告别阻塞,掌控并发
前端·微服务·架构·dubbo·safari
java水泥工1 小时前
基于Echarts+HTML5可视化数据大屏展示-监管系统
前端·echarts·html5·可视化大屏·大屏展示
大鱼前端1 小时前
大文件上传实战:基于Express、分片、Web Worker与压缩的完整方案
javascript·node.js
灵犀坠1 小时前
前端高频知识点汇总:从手写实现到工程化实践(面试&开发双视角)
开发语言·前端·javascript·tcp/ip·http·面试·职场和发展
Q_Q5110082851 小时前
python+django/flask+vue基于web的产品管理系统
前端·spring boot·python·django·flask·node.js
chilavert3181 小时前
技术演进中的开发沉思-229 Ajax:Firefox 与 Firebug
javascript·okhttp
方也_arkling1 小时前
【JS】日期对象及时间戳的使用(制作距离指定日期的倒计时)
开发语言·javascript·ecmascript
无奈何杨1 小时前
业务如何对接风控决策,实时/异步,结果同步
前端·后端