ref() 和 reactive() 的使用和区别

133 阅读4分钟

ref() 和 reactive() 的使用和区别

在 Vue 中,响应式是其最重要的特性之一。我们通常可以使用 ref()reactive() 来声明响应式状态,但它们之间存在一些重要的区别。本篇文章将详细解析这两者的不同点以及它们的应用场景。

ref()

在 Vue 3 的组合式 API 中,官方推荐使用 ref() 函数来声明基本数据类型的响应式状态:

import { ref } from 'vue'const name = ref('anyao')
const count = ref(0)

ref() 函数接收一个初始值,并将其包裹在一个带有 .value 属性的 ref 对象中返回:

console.log(count) // { value: 0 }
console.log(count.value) // 0
console.log(name.value) // anyao
​
count.value++
console.log(count.value) // 1

可以看到,使用 ref() 声明的响应式对象,需要通过 .value 来访问和修改值。

reactive()

另一种声明响应式状态的方式是使用 reactive() API。与 ref() 将基本类型的值包装在特定对象中的方式不同,reactive()对象本身 转化为响应式对象:

import { reactive } from 'vue'const state = reactive({ 
  name: 'anyao',
  count: 0
})

reactive() 会深层次地递归将对象内的属性转为响应式,因此当访问嵌套对象时,它们也会被响应式化。例如:

const state = reactive({
  user: {
    name: 'anyao',
    age: 20
  },
  posts: ['Vue', 'React']
})
​
// 修改嵌套对象中的属性
state.user.name = 'newName'
console.log(state.user.name) // 输出: newName

在这个示例中,state 中的嵌套对象 user 和数组 posts 也会被响应式化,我们可以直接修改它们的值并保持响应式特性。

此外,Vue 还提供了 shallowReactive() API,用于创建浅层响应式对象,避免深层次的响应式处理。它只会将对象的顶层属性设置为响应式,而不会递归地处理嵌套对象。例如:

import { shallowReactive } from 'vue'const state = shallowReactive({
  user: {
    name: 'anyao',
    age: 30
  }
})
​
// 顶层属性会响应式,但嵌套对象不会
state.user.name = 'newName'
console.log(state.user.name) // 输出: newName// 但是替换整个 user 对象,不会触发响应式
// 但 Vue 不会跟踪这个嵌套属性的变化
// 如果这个 name 被绑定在模板中,模板不会自动更新
state.user = { name: 'anotherName', age: 25 }
console.log(state.user.name) // 输出: anotherName

在这个例子中,虽然修改 state.user.name 会触发响应,但直接替换整个 user 对象并不会,因为 shallowReactive() 只处理顶层的属性。

reactive() 的局限性

虽然 reactive() 非常强大,但它在某些方面存在局限性:

  1. 有限的值类型reactive() 只能用于对象类型(如对象、数组以及 MapSet 这样的集合类型)。它无法处理基本类型值(如 stringnumberboolean)。对于这些类型的值,ref() 是更好的选择。

  2. 不能替换整个对象:Vue 的响应式追踪是通过对象属性的访问实现的,因此在使用 reactive() 时,必须保持对象的引用不变。如果替换了整个对象,响应式追踪将丢失:

    let state = reactive({ count: 0 })
    ​
    // 替换整个对象后,原有的响应式连接将丢失
    state = reactive({ count: 1 }) // 响应式跟踪已失效
    
  3. 对解构操作不友好:当我们将响应式对象的属性解构为独立的局部变量,或者将这些属性传递给函数时,响应式连接将丢失。为了避免这一问题,可以考虑使用 toRefs() 来保留响应性。

由于 reactive() 存在这些限制,建议在声明简单的原始类型响应式状态时优先使用 ref(),它更加直观,适用性更广。

用一张表对比

特性ref()reactive()
使用场景处理基本类型值(string, number, boolean)处理对象类型(对象、数组、Map、Set等)
访问方式通过 .value 属性访问和修改值直接访问或修改对象的属性
响应式深度仅对基本类型的值响应式,不自动递归自动递归处理嵌套对象,深度响应式
对象处理内部会将对象自动转为响应式直接使整个对象响应式
替换对象可以替换整个对象,保持响应式不能直接替换整个对象,否则会失去响应式追踪
适用场景适合处理单一值或基本类型的响应式状态适合处理复杂对象或数组的响应式状态
局限性需要通过 .value 访问不能替换整个对象,解构时响应式追踪丢失
其他 API支持 shallowRef(),创建浅层响应式支持 shallowReactive(),创建浅层响应式

原文链接: anyaowl.cn/blog/detail…