Vue 3 的 Composition API 为状态管理带来了全新的方式,其中最核心的两个 API 就是 ref 和 reactive。它们看似简单,但在实际开发中,很多开发者对二者的使用场景和底层差异存在模糊认识,甚至写出一些“能跑但不对”的代码。本文将从响应式原理出发,系统梳理 ref 与 reactive 的适用场景,并指出常见的误区与最佳实践。
一、理解响应式的基本原理
在 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 只能代理对象,对于 string、number、boolean 等基本类型,必须使用 ref。
const loading = ref(false)
const keyword = ref('')
const count = ref(0)
2. 需要整体替换的对象或数组
当某个状态可能被整体替换时,ref 比 reactive 更合适。因为 reactive 返回的代理对象一旦被重新赋值,会丢失响应性。
const list = ref([])
// 可以安全地整体替换
list.value = await fetchList()
const user = ref({ name: 'Tom' })
user.value = { name: 'Jerry' } // 仍然响应式
如果使用 reactive,state = 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.name 比 form.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 }
}
六、总结
ref 和 reactive 并非对立关系,而是互补工具。ref 解决了基本类型和整体替换的问题,reactive 提供了对象操作的简洁性。真正需要警惕的是:解构丢失响应性、整体替换 reactive、忘记 .value 等常见陷阱。理解它们背后的 Proxy 与 RefImpl 机制,才能在项目中做出合理选择,写出既正确又易维护的 Vue 3 代码。
在实际开发中,建议团队制定统一规范:以 ref 为主,reactive 为辅,配合 toRefs 处理解构,从而在灵活性与可读性之间取得平衡。
未经允许不得转载:任鹏个人博客 » Vue 3 中 ref 与 reactive 的使用场景与常见误区


朋友圈点赞图在线生成源码