Vue 3 中 ref 与 reactive 的使用场景与常见误区

Vue 3 的 Composition API 为状态管理带来了全新的方式,其中最核心的两个 API 就是 refreactive。它们看似简单,但在实际开发中,很多开发者对二者的使用场景和底层差异存在模糊认识,甚至写出一些“能跑但不对”的代码。本文将从响应式原理出发,系统梳理 refreactive 的适用场景,并指出常见的误区与最佳实践。

一、理解响应式的基本原理

在 Vue 3 中,响应式系统基于 ES6 的 Proxy 实现。reactive 接收一个普通对象,返回一个代理对象;而 ref 则是对基本类型值的包装,通过 .value 属性访问内部值。理解这一点,是区分二者使用场景的前提。

import { ref, reactive } from 'vue'

const count = ref(0)          // RefImpl 对象
const state = reactive({ n: 0 }) // Proxy 代理对象

ref 的本质是 RefImpl 类的实例,它通过 get value / set value 拦截器实现依赖收集和触发更新。当传入的是对象时,ref 内部会调用 reactive 对其进行深层代理。

二、ref 的典型使用场景

1. 基本类型值的响应式

这是 ref 最不可替代的场景。由于 reactive 只能代理对象,对于 stringnumberboolean 等基本类型,必须使用 ref

const loading = ref(false)
const keyword = ref('')
const count = ref(0)

2. 需要整体替换的对象或数组

当某个状态可能被整体替换时,refreactive 更合适。因为 reactive 返回的代理对象一旦被重新赋值,会丢失响应性。

const list = ref([])
// 可以安全地整体替换
list.value = await fetchList()

const user = ref({ name: 'Tom' })
user.value = { name: 'Jerry' } // 仍然响应式

如果使用 reactivestate = newObj 会断开与原始代理的关联,导致视图不再更新。

3. 需要解构或传递响应式引用

ref 作为独立对象,可以自由传递、解构(配合 toRefs 或直接传递 ref 本身),非常适合在组合函数(composable)之间共享状态。

// composables/useCounter.js
export function useCounter() {
  const count = ref(0)
  const increment = () => count.value++
  return { count, increment }
}

三、reactive 的典型使用场景

1. 结构固定的复杂对象

当一个状态对象的字段相对固定,且不需要整体替换时,reactive 更加简洁,访问属性时无需 .value

const form = reactive({
  username: '',
  password: '',
  remember: false
})

2. 表单状态管理

表单是 reactive 的经典场景:字段多、结构清晰、逐字段修改。

const form = reactive({ name: '', email: '' })
function reset() {
  form.name = ''
  form.email = ''
}

3. 与模板配合的嵌套对象

模板中直接访问 form.nameform.value.name 更直观,这也是很多团队偏好 reactive 的原因。

四、常见误区与陷阱

误区一:用 reactive 定义基本类型

const count = reactive(0) // ❌ 无效,reactive 只能接收对象

reactive 对基本类型无效,必须使用 ref

误区二:解构 reactive 丢失响应性

const state = reactive({ count: 0 })
let { count } = state // ❌ count 是普通数字,失去响应性
count++ // 视图不会更新

正确做法是使用 toRefs

const { count } = toRefs(state) // ✅ count 是 ref
count.value++

误区三:整体替换 reactive 对象

let state = reactive({ list: [] })
state = { list: [1, 2, 3] } // ❌ 丢失响应性

应改为修改属性,或改用 ref

误区四:在 ref 中忘记 .value

const count = ref(0)
count++ // ❌ 无效,实际是给 count 对象加属性
count.value++ // ✅

在模板中 Vue 会自动解包,但在 <script setup> 的 JS 逻辑中必须显式使用 .value

误区五:认为 reactive 一定比 ref 性能好

两者底层都基于 Proxy,性能差异可以忽略。选择应基于语义和可维护性,而非性能臆测。

误区六:ref 嵌套 ref 时的自动解包

const a = ref(1)
const b = ref(a) // b.value 是 a(ref 对象),不是 1

在数组或集合中,ref 不会自动解包,需要手动 .value

五、选择建议与最佳实践

综合来看,可以遵循以下原则:

  • 基本类型一律用 ref,这是硬性要求。
  • 对象/数组优先考虑 ref,尤其是需要整体替换或跨模块传递时。
  • 结构固定的表单、配置对象可用 reactive,代码更简洁。
  • 需要解构时,用 toRefs 转换,或直接使用 ref
  • 组合函数返回值统一用 ref,调用方处理更灵活。
  • 不要混用导致混乱:一个状态要么是 ref,要么是 reactive,保持一致性。
// 推荐:组合函数返回 ref
export function useUser() {
  const user = ref(null)
  const loading = ref(false)
  async function fetchUser(id) {
    loading.value = true
    user.value = await api.getUser(id)
    loading.value = false
  }
  return { user, loading, fetchUser }
}

六、总结

refreactive 并非对立关系,而是互补工具。ref 解决了基本类型和整体替换的问题,reactive 提供了对象操作的简洁性。真正需要警惕的是:解构丢失响应性、整体替换 reactive、忘记 .value 等常见陷阱。理解它们背后的 ProxyRefImpl 机制,才能在项目中做出合理选择,写出既正确又易维护的 Vue 3 代码。

在实际开发中,建议团队制定统一规范:以 ref 为主,reactive 为辅,配合 toRefs 处理解构,从而在灵活性与可读性之间取得平衡。

未经允许不得转载:任鹏个人博客 » Vue 3 中 ref 与 reactive 的使用场景与常见误区

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏