Vue3 响应式实战:ref 和 reactive 怎么选?数组场景全解析
前言
在 Vue3 的组合式 API 里,响应式数据是绕不开的核心 —— 而ref和reactive就是实现响应式的两大主力。很多刚上手的同学会困惑:这俩到底有啥区别?什么时候用ref,什么时候用reactive?尤其是处理数组的时候,总担心写着写着响应性就丢了。今天就用最接地气的方式,把这两个 API 的用法、场景和数组实战讲明白,新手也能一看就懂。
一、先搞懂:ref 和 reactive 到底不一样在哪?
虽然ref和reactive底层都是靠 Proxy 实现响应式,但从用法到适用场景,差别其实很明显,咱们从实际开发最关心的几个点拆解:
1. 能处理的数据类型不一样
ref是个 “多面手”—— 不管是数字、字符串、布尔值这种基本类型(比如计数器的数值、开关的状态),还是对象、数组这种引用类型,它都能 hold 住。而reactive有点 “挑食”—— 只能处理对象、数组这种引用类型,要是给它传个基本类型(比如reactive(123)),控制台不报错,但响应性直接失效,相当于白写。
2. 访问和修改的方式不一样
用ref包装的数据,在<script>里必须通过.value才能访问或修改(比如count.value += 1);但在<template>里不用,直接写count就行,Vue 会自动帮我们 “解包”。reactive就简单多了 —— 它包装的是对象 / 数组,直接通过属性名访问就行(比如user.name、list[0]),不用多写.value,跟操作普通对象没啥区别。
3. 重新赋值的时候,响应性会不会丢?
这是最容易踩坑的点!用ref的时候,哪怕是重新赋值整个对象 / 数组(比如list.value = [1,2,3]),响应性也不会丢 —— 因为ref内部帮我们把 “重新赋值” 这个操作也做了响应式处理。但reactive不行:如果直接给reactive包装的对象 / 数组 “整体替换”(比如let obj = reactive({name: '张三'}),然后写obj = {name: '李四'}),原来的 Proxy 代理就失效了,后续修改obj,页面根本不会更新 —— 相当于响应性 “断了”。
二、实战场景:什么时候该用 ref,什么时候用 reactive?
光说区别不够,得结合实际开发场景来选,这样才不会纠结:
用 ref 的场景:
- 基本类型必须用 ref:比如计数器(
const count = ref(0))、弹窗开关(const isShow = ref(false))、输入框的值(const inputVal = ref(''))—— 这些基本类型,只有ref能让它们变成响应式。 - 需要频繁替换整个引用类型:比如一个列表,有时候需要直接换成全新的数组(比如接口返回新数据,直接覆盖旧列表),这时候用
ref更省心,不用怕响应性丢了。 - 其实日常开发里,
ref的使用频率更高 —— 毕竟灵活,不管啥类型都能处理,新手先用ref也不容易出错。
用 reactive 的场景:
- 处理复杂对象,且主要改属性:比如用户信息对象(
const user = reactive({name: '张三', age: 20, address: '北京'})),我们平时只会改user.age或者user.address,不会直接把user换成新对象 —— 这种情况用reactive更直观,不用写.value。 - 简单说:如果你的操作是 “修改对象的属性”,而不是 “替换整个对象”,那
reactive会更顺手。
三、重点实战:数组场景下,ref 和 reactive 怎么用?
数组是开发里最常用的引用类型,也是最容易踩响应式坑的地方。咱们分别看两种 API 的正确写法,以及避坑要点。
场景 1:用 reactive 处理数组 —— 记得 “套个对象”!
前面说过,reactive直接包装数组时,一旦整体替换,响应性就丢了。所以实际开发里,推荐把数组作为对象的一个属性,用 reactive 包装这个对象—— 这样哪怕替换数组,响应性也不会断。
举个完整的例子,包含 “修改元素”“添加元素”“替换数组” 三种常见操作:
vue
<template>
<div class="array-demo">
<h3>reactive处理数组</h3>
<p>水果列表:{{ state.fruits.join('、') }}</p>
<button @click="modifyFirstFruit">把第一个水果改成草莓</button>
<button @click="addFruit" style="margin-left: 10px;">加一个葡萄</button>
<button @click="replaceWholeArray" style="margin-left: 10px;">换成热带水果</button>
</div>
</template>
<script setup>
import { reactive } from 'vue'
// 关键:数组fruits作为state对象的属性,用reactive包装state
const state = reactive({
fruits: ['苹果', '香蕉', '橙子']
})
// 1. 修改数组元素:直接操作state.fruits的索引
const modifyFirstFruit = () => {
state.fruits[0] = '草莓'
}
// 2. 给数组加元素:用数组的push方法
const addFruit = () => {
state.fruits.push('葡萄')
}
// 3. 替换整个数组:直接给state.fruits赋值新数组(响应性不丢)
const replaceWholeArray = () => {
state.fruits = ['西瓜', '菠萝', '芒果']
}
</script>
<style scoped>
button {
padding: 4px 8px;
cursor: pointer;
}
</style>
避坑提醒:千万别这么写!如果直接用reactive包装数组(const fruits = reactive(['苹果', '香蕉'])),然后执行fruits = ['西瓜', '菠萝']—— 这时候fruits就变成了普通数组,后续修改页面不会更新,响应性彻底丢了。
场景 2:用 ref 处理数组 —— 灵活,不怕替换
ref处理数组就没那么多讲究了,直接用ref包装数组就行。唯一要注意的是:在<script>里操作的时候,必须加.value。
同样用 “修改、添加、替换” 三个操作举例子,代码更简洁:
vue
<template>
<div class="array-demo">
<h3>ref处理数组</h3>
<p>运动列表:{{ sports.join('、') }}</p>
<button @click="modifyFirstSport">把第一个运动改成乒乓球</button>
<button @click="addSport" style="margin-left: 10px;">加一个羽毛球</button>
<button @click="replaceWholeSport" style="margin-left: 10px;">换成球类运动</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 直接用ref包装数组
const sports = ref(['足球', '篮球', '排球'])
// 1. 修改元素:加.value访问数组
const modifyFirstSport = () => {
sports.value[0] = '乒乓球'
}
// 2. 添加元素:push方法也要通过.value调用
const addSport = () => {
sports.value.push('羽毛球')
}
// 3. 替换整个数组:直接给.value赋值新数组,响应性不丢
const replaceWholeSport = () => {
sports.value = ['网球', '棒球', '高尔夫']
}
</script>
优势总结:用ref处理数组,最大的好处是 “灵活”—— 不管是修改元素、添加元素,还是直接替换整个数组,都不用考虑响应性丢失的问题,新手不容易踩坑。
四、最后给个 “懒人指南”:不用再纠结!
如果看完还是有点懵,记住这几个简单的原则,日常开发绝对够用:
- 基本类型数据:直接用
ref,别想太多; - 数组数据:优先用
ref,灵活且不容易丢响应性; - 复杂对象数据:如果只是修改属性(不替换整个对象),用
reactive(不用写.value);如果可能要替换整个对象,用ref; - 实在分不清的时候,用
ref准没错 —— 它的适用场景更广,容错率更高。
其实 Vue3 设计这两个 API,不是为了让我们 “二选一”,而是为了覆盖不同的开发场景。多写两个例子,很快就能找到手感~如果还有疑问,欢迎评论区交流,也可以点赞收藏,后续遇到数组响应式问题再回头看~
更多推荐


所有评论(0)