Vue 中的 ref 与 reactive

194 阅读4分钟

在 Vue 3 的组合式 API 中,refreactive 是创建响应式数据的两种主要方式。它们都用于使数据具有响应性,但在使用场景和特性上存在重要区别。

核心区别概览

特性refreactive
数据类型所有类型(原始值/对象)仅对象类型(Object/Array/Map/Set)
访问方式需要 .value(脚本中)直接访问属性
模板中使用自动解包(无需 .value直接访问属性
重新赋值支持(ref.value = newValue不支持(会失去响应性)
解包行为在 reactive 中自动解包不适用
TypeScript 支持更简单需要更复杂的类型定义

深入解析 ref

基本用法

import { ref } from 'vue';

// 创建 ref
const count = ref(0);
const user = ref({ name: 'John', age: 30 });

// 访问值(脚本中)
console.log(count.value); // 0
console.log(user.value.name); // 'John'

// 修改值
count.value = 1;
user.value.age = 31;

主要特性

  1. 支持所有数据类型:可以包装原始值(数字、字符串、布尔值)和对象
  2. .value 访问:在 JavaScript 代码中需要通过 .value 访问和修改
  3. 模板自动解包:在模板中直接使用变量名,无需 .value
    <template>
      <div>{{ count }}</div> <!-- 直接使用 count,而非 count.value -->
    </template>
    
  4. 可重新赋值:可以完全替换值而不丢失响应性
    user.value = { name: 'Alice', age: 25 }; // 有效
    

适用场景

  • 原始值(数字、字符串、布尔值)
  • 需要重新赋值的对象
  • 从组合函数返回响应式数据
  • 模板引用(DOM 元素引用)
    <input ref="inputRef">
    

深入解析 reactive

基本用法

import { reactive } from 'vue';

// 创建 reactive 对象
const state = reactive({
  count: 0,
  user: {
    name: 'John',
    address: {
      city: 'New York'
    }
  }
});

// 访问和修改
console.log(state.count); // 0
state.count = 1;

// 深层嵌套访问
console.log(state.user.address.city); // 'New York'
state.user.address.city = 'Los Angeles';

主要特性

  1. 仅限对象类型:只能用于对象、数组、Map、Set 等
  2. 深层响应性:嵌套对象自动转换为响应式
  3. 直接访问属性:不需要 .value 语法
  4. 不能重新赋值:重新赋值整个对象会破坏响应性
    // 错误用法 - 会失去响应性
    state = { count: 10 }; 
    
    // 正确用法 - 修改属性
    state.count = 10;
    
  5. 自动解包 ref:当 ref 作为 reactive 对象的属性时,会自动解包
    const count = ref(0);
    const state = reactive({ count });
    
    console.log(state.count); // 0 - 不需要 .value
    state.count = 1; // 相当于 count.value = 1
    

适用场景

  • 复杂的嵌套对象
  • 不需要重新赋值的状态对象
  • 表单对象
  • 需要深层响应的数据结构

关键区别详解

1. 数据类型支持

  • ref:可以包装任何值类型(原始值、对象、数组等)
  • reactive:只能处理对象类型(Object、Array、Map、Set)

2. 访问方式

// ref 需要 .value
const num = ref(0);
num.value = 1;

// reactive 直接访问
const obj = reactive({ num: 0 });
obj.num = 1;

3. 重新赋值能力

// ref 可以重新赋值
const data = ref({ count: 0 });
data.value = { count: 1 }; // 有效,保持响应性

// reactive 不能重新赋值
const state = reactive({ count: 0 });
state = { count: 1 }; // 无效,会破坏响应性

4. 在模板中的使用

两者在模板中都可以直接访问:

<!-- ref -->
<div>{{ count }}</div> 

<!-- reactive -->
<div>{{ state.count }}</div>

5. 解包行为

const count = ref(0);

// 在 reactive 中自动解包
const state = reactive({ count });
console.log(state.count); // 0 - 自动解包

// 在数组或集合中不会自动解包
const arr = reactive([count]);
console.log(arr[0].value); // 需要 .value

最佳实践与使用建议

  1. 优先使用 ref 的情况

    • 处理原始值(数字、字符串等)
    • 需要重新赋值的变量
    • 从组合函数返回响应式数据
    • 简单的响应式状态
  2. 优先使用 reactive 的情况

    • 复杂的嵌套对象状态
    • 不需要重新赋值的状态容器
    • 表单处理对象
    • 当需要深层响应性时
  3. 混合使用模式

    import { ref, reactive } from 'vue';
    
    export function useUser() {
      const loading = ref(false);
      const error = ref(null);
      
      const user = reactive({
        name: '',
        email: '',
        preferences: {
          theme: 'light',
          notifications: true
        }
      });
      
      async function fetchUser(id) {
        loading.value = true;
        try {
          const response = await fetch(`/api/users/${id}`);
          const data = await response.json();
          Object.assign(user, data); // 正确更新 reactive 对象
        } catch (err) {
          error.value = err.message;
        } finally {
          loading.value = false;
        }
      }
      
      return {
        loading,
        error,
        user,
        fetchUser
      };
    }
    
  4. 解构注意事项

    • 直接解构 reactive 对象会失去响应性:
      const state = reactive({ count: 0 });
      
      // 错误 - 失去响应性
      const { count } = state;
      
    • 使用 toRefs 保持响应性:
      import { toRefs } from 'vue';
      
      const state = reactive({ count: 0 });
      const { count } = toRefs(state); // 保持响应性
      

性能考虑

  • ref:对于原始值有轻微性能开销(需要包装对象)
  • reactive:处理大型对象时有更好的性能
  • 实际应用中性能差异通常可以忽略不计
  • 避免过度使用响应式系统,只在需要时使用

总结

场景推荐使用
原始值(数字、字符串等)ref
需要重新赋值的对象ref
组合函数返回值ref
模板引用(DOM 元素)ref
复杂嵌套对象reactive
不需要重新赋值的状态容器reactive
表单处理对象reactive

在 Vue 3 开发中,理解 refreactive 的区别至关重要。大多数情况下,我会推荐:

  • 优先使用 ref:因其更简单直观,适用于大多数场景
  • 在需要时使用 reactive:处理复杂嵌套对象时更优雅

两者不是互斥的,而是互补的。在实际项目中,经常混合使用它们,利用各自的优势创建更清晰、更易维护的代码结构。