Vue3 中ref和reactive的核心区别,这是掌握组合式 API 的关键知识点!我会从「本质差异→使用场景→代码示例→选择原则」四个维度,用通俗的语言帮你彻底分清两者,还会解决你可能遇到的 "解构丢失响应式" 等实际问题。
一、核心区别总览(先记结论)
ref和reactive都是 Vue3 创建响应式数据的 API,但核心定位不同:
ref:主打「基本类型」(String/Number/Boolean),也支持复杂类型(对象 / 数组),通过.value访问 / 修改;reactive:主打「复杂类型」(对象 / 数组),无需.value,直接操作属性,但不支持基本类型。
下面用表格直观对比所有差异点:
| 对比维度 | ref | reactive |
|---|---|---|
| 适用数据类型 | 基本类型(优先)、复杂类型(兼容) | 仅复杂类型(对象 / 数组),不支持基本类型 |
| 访问 / 修改方式 | 逻辑中:变量.value;模板中:直接用 |
逻辑 + 模板中:直接操作属性(无需.value) |
| 解构特性 | 解构后仍保持响应式(需注意赋值) | 直接解构会丢失响应式(需配合toRefs) |
| 新增属性支持 | 支持(复杂类型时,内部自动用 reactive) | 支持(对象 / 数组可直接新增属性 / 元素) |
| 顶层属性响应式 | 基本类型时天然支持;复杂类型时顶层属性变化支持 | 仅支持嵌套属性变化,顶层替换对象会丢失响应式 |
| 类型推导(TS) | 自动推导,类型友好 | 需手动指定类型(复杂对象时),略繁琐 |
二、逐个差异点详解(配代码示例)
1. 适用数据类型:基本类型 vs 复杂类型
这是最核心的区别,直接决定了该选哪个 API。
ref:优先用于基本类型,兼容复杂类型
vue
<script setup>
import { ref } from 'vue'
// ✅ 基本类型(推荐用法)
const name = ref('张三') // String
const age = ref(18) // Number
const isStudent = ref(true) // Boolean
// ✅ 复杂类型(兼容用法,内部自动转为reactive)
const user = ref({
address: '北京',
hobby: ['篮球']
})
// 修改数据:必须用 .value
name.value = '李四'
user.value.address = '上海' // 复杂类型时,.value 拿到的是reactive对象
user.value.hobby.push('游戏')
</script>
reactive:仅支持复杂类型,基本类型无效
vue
<script setup>
import { reactive } from 'vue'
// ❌ 错误用法:基本类型用reactive,不会变成响应式
const age = reactive(18)
age = 20 // 毫无反应,不是响应式
// ✅ 正确用法:对象类型
const user = reactive({
name: '张三',
age: 18
})
// ✅ 正确用法:数组类型
const list = reactive(['苹果', '香蕉'])
// 修改数据:直接操作属性/元素
user.name = '李四'
list.push('橘子')
</script>
2. 访问方式:.value 的有无
这是新手最容易踩坑的点,记住一句话:ref需要.value,reactive不需要。
ref:逻辑中必须加.value,模板中不用
vue
<template>
<!-- 模板中:直接用,无需.value -->
<p>姓名:{{ name }}</p>
<p>年龄:{{ age }}</p>
</template>
<script setup>
import { ref } from 'vue'
const name = ref('张三')
const age = ref(18)
// 逻辑中:必须加 .value 才能访问/修改
const updateUser = () => {
name.value = '李四' // 正确:修改生效,视图更新
age.value += 1 // 正确:年龄变成19
// age = 19 // 错误:直接赋值会覆盖ref对象,丢失响应式
}
</script>
reactive:全程无需.value,符合直觉
vue
<template>
<!-- 模板中:直接用属性 -->
<p>姓名:{{ user.name }}</p>
<p>爱好:{{ list[0] }}</p>
</template>
<script setup>
import { reactive } from 'vue'
const user = reactive({ name: '张三' })
const list = reactive(['篮球'])
// 逻辑中:直接修改属性,无需.value
const updateData = () => {
user.name = '李四' // 正确:视图更新
list[0] = '游戏' // 正确:视图更新
}
</script>
3. 解构特性:是否需要toRefs
解构是日常开发常用操作,但reactive直接解构会丢失响应式,ref则不会。
ref:解构后仍保持响应式
vue
<script setup>
import { ref } from 'vue'
const user = ref({
name: '张三',
age: 18
})
// 解构:直接解构ref对象的属性
const { name, age } = user.value // 注意:这里解构的是user.value(reactive对象)
// 修改:name和age是ref类型,需要.value
name.value = '李四' // 正确:user.name同步更新,响应式不丢失
age.value = 20 // 正确:视图更新
</script>
reactive:直接解构丢失响应式,需用toRefs
vue
<script setup>
import { reactive, toRefs } from 'vue'
const user = reactive({
name: '张三',
age: 18
})
// ❌ 错误:直接解构,丢失响应式
const { name, age } = user
name = '李四' // 视图不更新,响应式失效
// ✅ 正确:用toRefs解构,保持响应式
const { name: newName, age: newAge } = toRefs(user)
newName.value = '李四' // 正确:需要.value,视图更新
newAge.value = 20 // 正确:user.age同步更新
</script>
4. 顶层属性响应式:替换对象的差异
当需要「替换整个对象」时,ref和reactive的表现不同:
ref:替换整个对象仍保持响应式
vue
<script setup>
import { ref } from 'vue'
const user = ref({ name: '张三', age: 18 })
// 直接替换整个对象:响应式不丢失
user.value = { name: '李四', age: 20 } // 正确:视图更新
</script>
reactive:替换整个对象会丢失响应式
vue
<script setup>
import { reactive } from 'vue'
const user = reactive({ name: '张三', age: 18 })
// ❌ 错误:替换整个对象,丢失响应式
user = { name: '李四', age: 20 } // 后续修改user.name,视图不更新
// ✅ 正确:不替换对象,只修改属性
user.name = '李四'
user.age = 20 // 响应式正常
</script>
三、实际开发中的选择原则(新手直接照做)
不用纠结,按以下优先级选择即可:
- 基本类型(String/Number/Boolean) :优先用
ref例:const count = ref(0)、const username = ref('') - 复杂类型(对象 / 数组) :优先用
reactive例:const form = reactive({ username: '', password: '' })、const list = reactive([]) - 需要解构复杂类型 :
reactive + toRefs例:const { username, password } = toRefs(form) - 需要替换整个对象 :用
ref例:接口请求后需要替换整个数据对象时,data.value = res.data - TypeScript 开发 :
- 基本类型:
ref(自动推导类型,无需额外配置) - 复杂类型:
reactive+ 接口定义(例:const user: User = reactive({...}))
- 基本类型:
总结
- 核心区别 :
ref主打基本类型(需.value),reactive主打复杂类型(无需.value); - 关键坑点 :
reactive直接解构丢响应式(用toRefs解决),ref修改必须加.value; - 选择原则 :基本类型用
ref,复杂类型用reactive,解构用toRefs,替换对象用ref。
记住这 3 点,开发中就不会再混淆两者的用法啦!如果遇到具体场景不确定选哪个,不妨把数据类型和需求(是否解构、是否替换对象)列出来,对照上面的原则就能快速判断~