Skip to content

Vue 3 中 reactive() 与 ref() 的深度对比

Vue 3 Composition API 中的 reactive()ref() 是创建响应式数据的两个核心函数。虽然它们都能让数据变成响应式,但在设计目标、使用方式和内部原理上存在明显的区别。

核心区别概述

reactive()

  • 设计目标:专门用于将复杂的对象类型(Object、Array、Map、Set 等)转换成响应式对象
  • 特点:深层响应式,修改嵌套属性时也会触发更新
  • 参数限制:必须是对象或数组,不能是基本数据类型

ref()

  • 设计目标:更加通用,可以接收任何类型的值
  • 特点:通过 .value 属性包装值来实现响应式
  • 参数类型:支持基本数据类型和对象类型

详细对比分析

1. 参数类型与使用方式

reactive() 的使用

javascript
import { reactive } from 'vue';

// ✅ 正确用法 - 对象类型
const state = reactive({
  count: 0,
  user: { name: 'Alice' }
});

state.count++;           // 视图会更新
state.user.name = 'Bob'; // 视图会更新

// ❌ 错误用法 - 基本类型
const count = reactive(0); // 会收到警告,count 不是响应式的

ref() 的使用

javascript
import { ref } from 'vue';

// ✅ 用于基本类型
const count = ref(0);
console.log(count.value); // 0
count.value++;           // 必须通过 .value 修改

// ✅ 用于对象类型
const state = ref({ name: 'Alice' });
state.value.name = 'Bob'; // 访问和修改也需要 .value

2. 访问和修改方式

reactive() - 直接访问

javascript
const state = reactive({
  count: 0,
  items: ['a', 'b', 'c']
});

// 直接访问,就像普通对象
state.count = 10;
state.items.push('d');

// ⚠️ 解构赋值问题
let { count } = state; // count 失去响应性
count++; // state.count 不会改变,视图不会更新

ref() - 通过 .value 访问

javascript
const count = ref(0);
const items = ref(['a', 'b', 'c']);

// JavaScript 中必须使用 .value
count.value = 10;
items.value.push('d');

// 在模板中自动解包,无需 .value
// <template>
//   <div>{{ count }}</div> <!-- 不是 {{ count.value }} -->
// </template>

3. 模板中的表现

模板自动解包

vue
<template>
  <div>
    <!-- reactive 对象直接使用 -->
    <p>State count: {{ state.count }}</p>
    
    <!-- ref 自动解包,无需 .value -->
    <p>Ref count: {{ count }}</p>
    
    <!-- 嵌套在对象中的 ref 不会自动解包 -->
    <p>Nested ref: {{ obj.count.value }}</p>
  </div>
</template>

<script setup>
import { reactive, ref } from 'vue';

const state = reactive({ count: 0 });
const count = ref(0);
const obj = { count: ref(0) }; // 嵌套的 ref 需要手动 .value
</script>

4. 内部实现原理

reactive() 的原理

javascript
// 简化的实现原理
function reactive(target) {
  return new Proxy(target, {
    get(target, key) {
      track(target, key); // 依赖收集
      return target[key];
    },
    set(target, key, value) {
      target[key] = value;
      trigger(target, key); // 派发更新
      return true;
    }
  });
}
  • 利用 ES6 的 Proxy 对对象进行代理
  • 拦截所有操作(get, set, deleteProperty 等)
  • 实现深度响应式

ref() 的原理

javascript
// 简化的实现原理
function ref(value) {
  return {
    get value() {
      track(this, 'value'); // 依赖收集
      return this._value;
    },
    set value(newValue) {
      this._value = newValue;
      trigger(this, 'value'); // 派发更新
    },
    _value: value
  };
}
  • 创建包装对象,通过 value 属性的 getter/setter 实现响应式
  • 如果值是对象,内部会调用 reactive() 处理

实际应用场景

使用 reactive() 的场景

javascript
// ✅ 表单状态管理
const formState = reactive({
  username: '',
  email: '',
  profile: {
    age: null,
    city: ''
  }
});

// ✅ 列表数据管理
const listState = reactive({
  items: [],
  loading: false,
  total: 0
});

使用 ref() 的场景

javascript
// ✅ 简单状态
const loading = ref(false);
const count = ref(0);

// ✅ DOM 元素引用
const inputRef = ref(null);

// ✅ 可能改变类型的数据
const data = ref(null); // 可能是 null 或对象

性能考虑

reactive() 的性能特点

  • 优势:访问属性时无需 .value,代码更简洁
  • 劣势
    • 对整个对象进行代理,内存占用相对较大
    • 解构会失去响应性,需要使用 toRefs() 等工具函数

ref() 的性能特点

  • 优势
    • 更精确的响应式控制
    • 不会因解构失去响应性
    • 对于基本类型更高效
  • 劣势:需要频繁使用 .value,可能影响代码可读性

最佳实践建议

1. 优先使用 ref()

javascript
// ✅ 推荐:统一使用 ref
const count = ref(0);
const user = ref({ name: 'Alice', age: 25 });
const items = ref([]);

// 一致的访问模式
count.value++;
user.value.name = 'Bob';
items.value.push('new item');

2. reactive() 适用场景

javascript
// ✅ 复杂的嵌套对象状态
const appState = reactive({
  user: {
    profile: { name: '', avatar: '' },
    settings: { theme: 'light', lang: 'zh' }
  },
  ui: {
    sidebar: { collapsed: false },
    modal: { visible: false, title: '' }
  }
});

3. 混合使用策略

javascript
// ✅ 根据数据特点选择合适的API
const useUserStore = () => {
  // 简单状态用 ref
  const loading = ref(false);
  const error = ref(null);
  
  // 复杂对象用 reactive
  const userInfo = reactive({
    profile: { name: '', email: '' },
    permissions: []
  });
  
  return {
    loading,
    error,
    userInfo
  };
};

对比总结表

特性reactive()ref()
参数类型只能是对象/数组任意类型
JS 中访问直接访问 (state.prop)通过 .value 访问 (count.value)
模板中访问直接访问 (state.prop)自动解包 (count)
响应式丢失解构会丢失响应性不会,操作的是 ref 对象本身
底层原理Proxy 代理包装对象 + getter/setter
内存占用相对较大(整个对象代理)相对较小(只包装 value)
类型推导较好的 TypeScript 支持优秀的 TypeScript 支持

总结

理解 reactive()ref() 的区别是掌握 Vue 3 Composition API 的关键。选择哪个API主要取决于:

  1. 数据类型:基本类型必须用 ref(),复杂对象两者都可以
  2. 使用习惯:偏好直接访问选择 reactive(),偏好统一模式选择 ref()
  3. 团队约定:保持团队代码风格的一致性

无论选择哪种方式,重要的是理解它们的工作原理,这样才能写出健壮、可维护的 Vue 3 应用程序。

基于 VitePress 构建