一、前言
前几篇代码里一直在写 ref,今天终于把它讲透。这是 Vue3 响应式的第一块基石:什么是响应式?为什么 script 里要 .value,模板里又不用?为什么普通变量页面不更新?一篇全部搞懂。
二、先理解:什么是响应式
写个反例感受一下,用普通变量渲染页面:
vue
<template>
<p>{{ msg }}</p>
<button @click="msg = '我变了'">改数据</button>
</template>
<script setup>
// 普通变量,没有包 ref
let msg = '我一开始的样子'
</script>
点击按钮,页面纹丝不动。因为 Vue 根本不知道 msg 变了------它不是响应式数据。
换成 ref 包一层:
vue
<template>
<p>{{ msg }}</p>
<button @click="msg = '我变了'">改数据</button>
</template>
<script setup>
import { ref } from 'vue'
// ref 包一层,变成响应式数据
const msg = ref('我一开始的样子')
</script>
再点,页面立刻更新 。这就是响应式:数据变化时,用到它的页面部分自动更新。
三、ref 的本质:给数据套一层"监控壳"
通俗理解:ref(初始值) 会把你的数据装进一个带监控的盒子,Vue 盯着这个盒子,数据一有风吹草动,立刻通知页面更新。
在 script 里打印看看 ref 到底长什么样:
js
const count = ref(0)
console.log(count)
// 输出:RefImpl { _value: 0, ... }
它是个对象,真正的值藏在 .value 属性里。所以------
script 里操作数据,必须通过 .value:
js
const count = ref(0)
count.value // 读:拿到 0
count.value = 10 // 写:改成 10,页面自动更新
count.value += 5 // 自增:变成 15
console.log(count) // ❌ 打印的是盒子对象,不是 0
console.log(count.value) // ✅ 打印 0
模板里不用 .value,直接写名字:
vue
<template>
<!-- ✅ 直接写 count,不用 count.value -->
<p>{{ count }}</p>
</template>
为什么模板里不用?因为 Vue 编译模板时会自动帮你解包 (自动加 .value)。这不是语法不一致,是 Vue 设计好的------模板里写 .value 反而多余。
一句话记住:script 里要 .value,模板里不用。
四、ref 包对象:深层响应式
ref 不只能包数字字符串,包对象、数组也完全没问题,而且是深层响应式------嵌套多深的属性改了都会更新:
vue
<template>
<p>姓名:{{ user.name }}</p>
<p>城市:{{ user.address.city }}</p>
<button @click="user.address.city = '上海'">搬家</button>
</template>
<script setup>
import { ref } from 'vue'
const user = ref({
name: '小明',
address: {
city: '北京' // 深层嵌套,依然响应式
}
})
</script>
点按钮,"城市:上海"立刻更新。深层属性通过 .value 一路点下去访问,模板里 Vue 自动解包,直接 user.address.city 就行。
五、三个高频新手坑(重点看)
坑 1:忘了 .value,页面不更新
js
const count = ref(0)
function add() {
count++ // ❌ 改的是盒子上的临时值,页面不动
count.value++ // ✅ 正确
}
现象:代码不报错(或报 NaN),但页面死活不更新。90% 是忘写 .value。
坑 2:解构后丢失响应式
js
const count = ref(0)
// ❌ 解构的一瞬间,就把"当时的值 0"复制出来了
// 之后 count.value 再怎么变,num 都是死的 0
let num = count.value
// ✅ 要用就用 count.value,别提前解构
(解构对象的场景要用 toRefs,那是 012 篇的主角,这里先埋个伏笔。)
坑 3:异步/延时后页面不更新?不存在的
js
const msg = ref('加载中')
// 定时器里改 ref,页面照样更新,放心用
setTimeout(() => {
msg.value = '加载完成'
}, 1000)
有些框架的数据脱离响应式要在特定时机刷新,Vue3 的 ref 在任何地方改都触发更新------这正是它的强大之处。
六、综合实战:点赞 + 自动切换文案
用 ref 写个完整交互小案例,把本篇知识点全用上:
vue
<template>
<h3>{{ article.title }}</h3>
<p>{{ liked ? '❤️ 已点赞' : '🤍 点个赞吧' }}</p>
<p>点赞数:{{ count }}</p>
<button @click="toggle">{{ liked ? '取消赞' : '点赞' }}</button>
</template>
<script setup>
import { ref } from 'vue'
const article = ref({ title: 'Vue3 ref 一篇讲透' })
const count = ref(66)
const liked = ref(false)
function toggle() {
if (liked.value) {
liked.value = false
count.value-- // 取消赞,数量减 1
} else {
liked.value = true
count.value++ // 点赞,数量加 1
}
}
</script>
运行后:点赞按钮在两种文案间切换,数字实时增减,emoji 同步变化------三个 ref 数据联动,页面全自动更新。
【截图位置:点赞前(灰心+66)与点赞后(红心+67)的对比效果】
七、今日小结
- 响应式 = 数据变,视图自动变
ref(初始值)创建响应式数据,真值存在.value里- script 里
.value,模板里不用 - ref 包对象是深层响应式
- 三大坑:忘 .value、提前解构、(误以为)异步不更新
作者:陈志宝
下篇预告
ref 什么都能包,但每次都 .value 有点烦?下一篇讲它的好兄弟 reactive:专门包对象、不用 .value,以及两者到底该选谁。