ref和reactive

216 阅读4分钟

一、基础概念与本质区别

1. ref 的本质

  • 定义:通过 ref() 函数创建响应式引用,可接收任意类型数据(基本类型/对象/数组)。
  • 实现原理:将数据包裹在 { value: 数据 } 的对象中,通过 Proxy 监听 value 的访问与修改。
  • 使用场景:基本类型数据(如 numberstring),或需要明确声明单个响应式变量时。

示例

import { ref } from 'vue';
const count = ref(0);
console.log(count.value); // 0
count.value = 1; // 修改值

2. reactive 的本质

  • 定义:通过 reactive() 函数创建响应式对象,只能接收对象类型数据(对象/数组/Map 等)。
  • 实现原理:直接对原始对象创建 Proxy 代理,监听所有属性的读写操作。
  • 使用场景:对象或数组等复杂数据结构,需要批量处理响应式状态时。

示例

import { reactive } from 'vue';
const state = reactive({ count: 0 });
console.log(state.count); // 0
state.count = 1; // 修改值

二、核心差异对比(面试高频)

维度refreactive
数据类型支持基本类型和对象类型仅支持对象类型(需包裹对象)
响应式原理包裹为 { value } 的 Proxy 对象直接代理原始对象的所有属性
访问方式通过 .value 访问和修改直接通过属性访问(如 state.foo
解构丢失响应式解构后需用 toRefs 保持响应式解构即丢失响应式(需用 toRef
性能消耗每次访问需通过 .value,略高直接访问属性,性能更优

三、面试必问场景与解决方案

1. 为什么 ref 可以处理基本类型,而 reactive 不行?

  • 原因
    • reactive 基于 Proxy,只能代理对象类型,无法直接监听基本类型的变化。
    • ref 通过包裹成 { value } 的对象,将基本类型转为对象类型,从而实现响应式。

2. 解构 ref/reactive 时如何保持响应式?

  • 场景示例
    const state = reactive({ count: 0, name: 'Vue' });
    // 错误:解构会丢失响应式
    const { count } = state; 
    count++; // 无法触发视图更新
    
    const num = ref(0);
    // 错误:解构直接获取 value,丢失响应式
    const { value } = num; 
    value++; // 无效
    
  • 解决方案
    • reactive 解构:使用 toRefstoRef
      import { toRefs } from 'vue';
      const state = reactive({ count: 0 });
      const { count } = toRefs(state); // count 是 ref 对象,保留响应式
      
    • ref 解构:直接使用 { count } = num 会丢失响应式,需通过 value 访问,或用 toRefs 包裹后解构。

3. 数组操作时 ref 与 reactive 的差异

  • reactive 数组
    • 直接通过索引修改(如 arr[0] = 1不会触发更新,需用 Vue.set 或数组原生方法(push/splice 等)。
  • ref 数组
    • 本质是 ref({ value: 数组 }),通过 arr.value[0] = 1 修改会触发更新,或使用 update 方法:
      const arr = ref([1, 2, 3]);
      arr.value[0] = 0; // 有效
      arr.value.push(4); // 有效
      

四、进阶应用与最佳实践

1. 组合式 API 中的选择策略

  • 单个变量:优先用 ref(如计数器、布尔值)。
  • 对象/数组:优先用 reactive(如表单数据、列表)。
  • 混合场景:用 ref 包裹对象,避免频繁 toRefs
    const form = ref({
      name: '张三',
      age: 18
    });
    // 访问:form.value.name
    

2. 与 setup/suspense 的配合

  • ref 支持 unref 语法糖(自动解包 value):
    <template>
      <div>{{ count }}</div> <!-- 等价于 count.value,需在 setup 中用 unref(count) 或在模板中直接使用 -->
    </template>
    
    <script>
    import { ref, unref } from 'vue';
    setup() {
      const count = ref(0);
      // 模板中可直接用 count,内部自动调用 unref(count)
      return { count };
    }
    </script>
    

3. 性能优化场景

  • 大型对象:若对象属性很多且部分无需响应式,用 reactiveref 更高效(减少 Proxy 包裹层级)。
  • 避免冗余响应式:基本类型用 ref,复杂对象用 reactive,避免过度包裹。

五、问题

1. 什么时候用 ref,什么时候用 reactive?

  • 处理基本类型(如数字、字符串)时用 ref,因其能将基本类型转为响应式对象。
  • 处理对象或数组时用 reactive,直接代理原始数据更高效。
  • 当需要解构响应式数据并保持响应式时,ref 配合 toRefs 更便捷(如从组合函数返回状态时)。

2. ref 和 reactive 的响应式丢失场景及解决方法?

  • reactive 解构丢失:解构对象属性会脱离 Proxy 代理,需用 toRefstoRef 转换。
  • ref 解构丢失:直接解构 ref 对象会获取 value,失去响应式,需通过 value 访问或用 toRefs 包裹。
  • 数组索引修改reactive 数组直接修改索引不触发更新,需用 Vue.set 或数组方法;ref 数组需通过 value 修改。