4.2 Vue3中reactive与ref详解及区别

发布于:2025-08-15 ⋅ 阅读:(16) ⋅ 点赞:(0)

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. 特点

  • 只适用于对象/数组:不能用于基本类型(stringnumberbooleannullundefinedsymbol)。
  • 深层响应式:对嵌套对象和数组也是响应式的。
  • 直接访问:在 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>
      <div>{{ count }}</div>     <!-- 自动解包,显示 count.value -->
      <div>{{ user.age }}</div>  <!-- 自动解包,显示 user.value.age -->
      <button @click="count++">+</button> <!-- 模板中也无需 .value -->
    </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 对象,不是数组

三、reactive 与 ref 的核心区别

特性 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

    • 当你不确定数据类型时。
    • 当你需要一个基本类型的响应式变量时(如 countshowinputValue)。
    • 当你希望代码风格统一,减少 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 包装任何值,是处理基本类型和需要灵活替换场景的首选。
  • 两者可以结合使用,ref 在 reactive 对象中会被自动解包。
  • 选择哪个主要取决于你的数据结构、编码习惯和对解包/解构的偏好。在现代 Vue 开发中,ref 因其通用性而被广泛使用。

网站公告

今日签到

点亮在社区的每一天
去签到