1) ref 的本质:把"一个值"包进响应式盒子里📦
ref() 干的事很直白:
给一个值套个壳,壳里放
.value,Vue 只要看到这个壳的.value变了,就会触发依赖更新。
你可以把 ref 想成"带弹簧的盒子":你改盒子里的东西(.value),弹簧就弹一下(触发更新)。
✅ ref 的使用场景
- 基础类型(string/number/boolean)
- 需要整体替换的对象/数组(比如"直接换一份新数据")
- 需要明确以"单个状态"为单位传递/暴露(组合式函数 return 一个状态给外面用)
- 和模板配合简单 (模板里会自动帮你解
.value,写起来更顺)
例子:基础类型就老老实实 ref
js
import { ref } from 'vue'
const count = ref(0)
const name = ref('Daniel')
function inc() {
count.value++
}
例子:整体替换对象时 ref 很香
js
const user = ref({ id: 1, nick: 'Tom' })
// 整体换掉
user.value = { id: 2, nick: 'Jerry' }
小吐槽:你要是用
reactive,整体替换对象这事就没那么自然(后面会讲)。😅
2) reactive 的本质:把"对象本身"变成一个代理🪞
reactive() 更像是:
给对象套一层 Proxy,拦截 get/set,实现属性级别的依赖追踪与触发更新。
这意味着它天生适合:
- 结构化状态(多字段、嵌套对象)
- 频繁改对象的某个属性(而不是整体替换)
✅ reactive 的使用场景
- 表单对象(多字段输入、校验、联动)
- 业务状态对象(loading/error/data 分组)
- 嵌套结构(列表项、复杂配置)
例子:表单最适合 reactive
js
import { reactive } from 'vue'
const form = reactive({
username: '',
password: '',
remember: false
})
function reset() {
form.username = ''
form.password = ''
form.remember = false
}
例子:一个请求状态对象
js
const state = reactive({
loading: false,
error: null,
data: null
})
async function fetchData() {
state.loading = true
state.error = null
try {
// ...
state.data = { ok: true }
} catch (e) {
state.error = e
} finally {
state.loading = false
}
}
3) 解构为什么会失去响应式?这坑真是"新手必踩"😭
最经典翻车现场:
js
const form = reactive({ username: 'a', password: 'b' })
const { username } = form
username = 'xxx' // ❌ 改不了 form.username(而且还会报错:Assignment to constant variable)
你可能会说:那我不赋值,我只读呢?
js
const { username } = form
console.log(username) // 读到的是一个普通值快照
本质原因:
reactive的响应式能力来自 Proxy 拦截对象属性访问- 你一旦
const { username } = form,你取出来的是 当时的值,不是"代理的 getter" - 后续
form.username再变,username这个变量不会自动跟着变
👉 这就叫:响应式链路断了。
人话:你把"摄像头"拆下来放桌上了,它就不再跟着"监控系统"走了😅
4) toRef / toRefs:专治"解构失灵",给你把链路焊回去🔧
4.1 toRef:把对象的某个属性,变成一个 ref(仍然指向原对象)
js
import { reactive, toRef } from 'vue'
const form = reactive({ username: 'a', password: 'b' })
const usernameRef = toRef(form, 'username')
usernameRef.value = 'new' // ✅ 等价于 form.username = 'new'
你可以理解为:
toRef(form, 'username')创建了一个"带指针的 ref".value的读写其实映射到form.username
适用场景:
✅ 你只想拿其中一个字段出去用(比如子组件只关心 username)
4.2 toRefs:把 reactive 对象的所有属性都转成 ref(方便解构)
js
import { reactive, toRefs } from 'vue'
const form = reactive({ username: 'a', password: 'b' })
const { username, password } = toRefs(form)
username.value = 'u' // ✅ form.username 跟着变
password.value = 'p' // ✅ form.password 跟着变
适用场景:
✅ 你想解构出来很多字段,还要保持响应式
✅ 用在组合式函数 return 时特别爽(下面会讲选型)
5) 实战选型建议:别纠结,按"状态形态 + 使用方式"选就完事了😄
我给你一套"抄走就能用"的判断规则:
✅ 规则 1:单值、基础类型、开关、计数器 ------ 用 ref
js
const visible = ref(false)
const count = ref(0)
const keyword = ref('')
✅ 规则 2:表单、复杂对象、多字段联动 ------ 用 reactive
js
const form = reactive({ a: '', b: '', c: 0 })
✅ 规则 3:需要"整体替换"时 ------ ref 更顺手
比如接口返回一整个对象,你经常会这么写:
js
const detail = ref(null)
detail.value = await fetchDetail()
✅ 规则 4:组合式函数对外暴露状态 ------ 两种姿势都行,但要统一风格
写法 A:全 ref(return 解构最友好)
js
export function useCounter() {
const count = ref(0)
const inc = () => count.value++
return { count, inc }
}
写法 B:reactive + toRefs(既结构化又可解构)
js
export function useForm() {
const form = reactive({ username: '', password: '' })
const reset = () => { form.username = ''; form.password = '' }
return { ...toRefs(form), reset }
}
重点:别直接
return form然后外面解构,不然你就回到"响应式断链"的老路上了😭
✅ 规则 5:模板里想少写 .value ------ reactive 更舒服,但别滥用
模板里 reactive 直接 form.username 读写很自然;
ref 在模板里会自动解包 .value,其实也不麻烦。
真正要避免的是:为了少写 .value,把所有东西都塞 reactive,最后结构巨肥、依赖混乱、调试痛苦😅
最后一句"反问收尾"(拿去当文章结尾钩子😏)
你看,ref 和 reactive 从来不是"谁更高级",而是"谁更适合你当前的状态形态"。
所以我反问你一句:
**你现在纠结 ref/reactive,是在优化代码,还是在给未来的自己埋坑?**😂