4.2 Vue3中reactive与ref详解及区别

reactiveref 是 Vue 3 Composition API 中创建响应式数据的两个核心函数。它们都基于 Proxy 实现了数据的响应式,但使用场景和方式有所不同。


一、reactive

1. 基本概念

  • 作用 :将一个对象(或数组)转换为响应式对象。
  • 原理 :使用 Proxy 对传入的对象进行深度代理,拦截其所有属性的读取(get)和设置(set)操作。
  • 返回值 :返回一个代理对象(Proxy),该对象是原始对象的响应式副本。

2. 基本用法

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

// 响应式对象
const state = reactive({
  count: 0,
  name: 'Vue',
  user: {
    age: 25
  },
  list: [1, 2, 3]
})

// 直接修改属性
state.count++
state.name = 'Composition API'
state.user.age = 30
state.list.push(4)

// 在模板中使用
// <template>
//   <div>{{ state.count }}</div>
//   <div>{{ state.user.age }}</div>
// </template>

3. 特点

  • 只适用于对象/数组 :不能用于基本类型(string, number, boolean, null, undefined, symbol)。
  • 深层响应式:对嵌套对象和数组也是响应式的。
  • 直接访问 :在 JavaScript 和模板中都直接通过 .[] 访问属性,无需 .value
  • 代理对象 :返回的是一个 Proxy 对象,与原始对象不相等 (state !== originalObject)。

4. 注意事项

  • 解构会失去响应性

    复制代码
    const state = reactive({ count: 0, name: 'Alice' })
    
    // ❌ 错误:解构后 count 和 name 是普通变量,失去响应性
    const { count, name } = state
    
    count++ // 不会触发视图更新
    
    // ✅ 正确:使用 toRefs
    const { count, name } = toRefs(state)
    // 此时 count 和 name 是 ref,需要 .value
    count.value++ // ✅ 会触发更新
  • 替换整个对象会失去响应性

    复制代码
    const state = reactive({ count: 0 })
    
    // ❌ 错误:直接赋值一个新对象,会丢失响应性连接
    state = { count: 1 } // state 不再是响应式的
    
    // ✅ 正确:修改对象属性
    state.count = 1
    
    // ✅ 正确:如果需要替换,可以重新 reactive
    Object.assign(state, { count: 1, name: 'Bob' })
  • Set/Map/WeakSet/WeakMapreactive 也可以代理这些集合类型。


二、ref

1. 基本概念

  • 作用 :创建一个响应式引用。它可以包装任何类型的值(基本类型或对象)。
  • 原理
    • 对于基本类型 :创建一个包含 .value 属性的对象,并使用 Object.defineProperty (Vue 2) 或 Proxy (Vue 3) 使其响应式。
    • 对于对象类型 :内部会自动调用 reactive() 进行转换。
  • 返回值 :返回一个包含 .value 属性的对象

2. 基本用法

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

// 响应式基本类型
const count = ref(0)
const name = ref('Vue')
const isActive = ref(true)

// 修改值
count.value++ // 必须使用 .value
name.value = 'Composition API'

// 响应式对象 (内部调用 reactive)
const user = ref({
  age: 25
})
user.value.age = 30 // 注意:user 是 ref,user.value 是 reactive 对象

// 响应式数组
const list = ref([1, 2, 3])
list.value.push(4)

3. 特点

  • 通用性强 :可以用于任何类型的数据。

  • .value 访问

    • JavaScript 中读取或修改值时,必须 使用 .value

    • 模板 (template) 中使用时,Vue 会自动解包(unwrapping),无需 .value

      <template>
      {{ count }}
      {{ user.age }}
      <button @click="count++">+</button> </template>
  • 解包 (Unwrapping)

    • ref 被作为属性添加到 reactive 对象中时,会自动解包。
      • ref 内部,如果值是对象,也会自动转换为 reactive

        const count = ref(0)
        const state = reactive({
        count, // 自动解包,state.count 等同于 count.value
        name: 'Alice'
        })

        console.log(state.count) // 0 (直接访问,无需 .value)
        state.count++ // 相当于 count.value++
        console.log(count.value) // 1

4. 注意事项

  • JavaScript 中必须用 .value :忘记 .value 是常见错误。

  • 模板中自动解包:这是 Vue 的优化,让模板更简洁。

  • 数组索引ref 包装的数组,在 JavaScript 中访问元素仍需 .value

    复制代码
    const list = ref([1, 2, 3])
    console.log(list.value[0]) // ✅ 正确
    // console.log(list[0]) // ❌ 错误,list 是 ref 对象,不是数组

三、reactiveref 的核心区别

特性 reactive() ref()
适用类型 仅对象/数组 (Object, Array, Map, Set 等) 任何类型 (基本类型 + 对象/数组)
返回值 响应式代理对象 (Proxy) 包含 .value 的响应式对象
访问方式 (JS) 直接访问属性 (obj.prop) 必须通过 .value 访问 (ref.value)
访问方式 (模板) 直接访问 ({``{ obj.prop }}) 自动解包 ,直接访问 ({``{ ref }})
解构 直接解构会失去响应性 (需 toRefs) 解构后仍是 ref,需 .value
替换 替换整个对象会失去响应性 可以安全地替换 ref.value
性能 深层代理,可能稍重 基本类型轻量,对象内部用 reactive
类型推断 (TS) 类型保持不变 包装为 Ref<T>

四、如何选择?

  1. 优先使用 ref

    • 当你不确定数据类型时。
    • 当你需要一个基本类型 的响应式变量时(如 count, show, inputValue)。
    • 当你希望代码风格统一,减少 toRefs 的使用(尤其是在 <script setup> 中)。
    • 当你需要替换整个值 时(ref.value = newValue)。
  2. 使用 reactive

    • 当你有一个复杂的对象结构,并且希望直接操作其属性时。
    • 当你希望代码在 JavaScript 中看起来更"自然"(无需 .value)。
    • 注意解构问题,必要时配合 toRefs 使用。

推荐实践

  • <script setup> 语法糖中 :很多开发者倾向于统一使用 ref ,因为它更通用,且在模板中自动解包,JS 中虽然要 .value,但 IDE 通常能很好提示。这可以减少对 toRefs 的依赖。

  • 复杂状态对象 :如果有一个包含多个相关属性的大对象,使用 reactive 可能更直观,但记得用 toRefs 解构。

    // 推荐:统一使用 ref (尤其在 script setup 中)
    const count = ref(0)
    const name = ref('')
    const userList = ref([])

    // 或者:复杂对象用 reactive + toRefs
    const formState = reactive({
    name: '',
    email: '',
    age: 0
    })
    const { name, email, age } = toRefs(formState) // 解构后保持响应性


五、总结

  • reactive 是为对象量身定制的响应式解决方案,使用方便但有解构陷阱。
  • ref 是一个通用 的响应式容器,通过 .value 包装任何值,是处理基本类型和需要灵活替换场景的首选。
  • 两者可以结合使用,refreactive 对象中会被自动解包。
  • 选择哪个主要取决于你的数据结构、编码习惯和对解包/解构的偏好。在现代 Vue 开发中,ref 因其通用性而被广泛使用。