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 应用,避免常见陷阱,编写出更清晰、可维护的响应式代码。

相关推荐
A242073493012 小时前
深入浅出理解AJAX:核心原理与POST/GET区别详解
前端·ajax·okhttp
LYFlied12 小时前
【每日算法】LeetCode 300. 最长递增子序列
前端·数据结构·算法·leetcode·职场和发展
张较瘦_12 小时前
前端 | 代码可读性 + SEO 双提升!HTML 语义化标签实战教程
前端·html
似水流年QC12 小时前
前端国际化实战指南:i18n 工程化最佳实践总结
前端
GISer_Jing12 小时前
企业级前端脚手架:原理与实战指南
前端·前端框架
非凡ghost12 小时前
Floorp Browser(基于Firefox火狐浏览器)
前端·windows·学习·firefox·软件需求
hpz122312 小时前
XHR和Fetch功能对比表格
前端·网络请求
q_191328469513 小时前
基于SpringBoot+Vue.js的高校竞赛活动信息平台
vue.js·spring boot·后端·mysql·程序员·计算机毕业设计
我是小邵13 小时前
【踩坑实录】一次 H5 页面在 PC 端的滚动条与轮播图修复全过程(Vue + Vant)
前端·javascript·vue.js
苹果电脑的鑫鑫13 小时前
Css画圆弧的方法
前端·css