ref 是带响应式追踪的包装对象,必须通过 .value 赋值才能触发更新;模板中 {{ count }} 会被自动解包为 {{ count.value }},而 js 逻辑中需手动访问 .value。

Vue 的 ref 不是普通变量,而是一个被 Vue 响应式系统“盯住”的包装对象。你对 .value 的赋值,本质是触发了 Vue 内部的 setter 监听逻辑,从而通知依赖它的模板或计算属性更新。
ref 的真实结构:一个带监听能力的壳
当你写 const count = ref(0),Vue 实际创建的是类似这样的对象:
count = {
value: 0,
// Vue 在这里悄悄加了响应式追踪(基于 Proxy 或 defineProperty)
}
这个对象本身是响应式的,但真正存数据的是 value 属性。Vue 并不直接监控原始值 0,而是监控整个 count 对象及其 value 属性的变化。
为什么必须改 .value,而不是直接赋值?
改 .value 是在“壳里面换内容”,Vue 能捕获这次变更;直接赋值则是把整个壳扔掉,换一个普通变量:
-
count.value = 5→ Vue 检测到count的value属性被修改 → 触发更新 -
count = 5→count变成数字5,不再是 ref 对象 → Vue 失去追踪目标 → 视图静止
模板里不用 .value,是因为 Vue 自动做了代理
你在模板中写 {{ count }},Vue 编译器会自动展开为 {{ count.value }}。这不是语法糖的“省略”,而是模板上下文专门对 ref 类型做了透明解包——它只发生在模板渲染阶段,JS 逻辑中仍需手动访问 .value。
触发更新的关键:setter 被调用 + 依赖被收集
视图更新不是靠“值变了”就发生,而是两个条件同时满足:
- 你在
setup()或组件逻辑中执行了ref.value = newValue,触发了 Vue 注入的 setter - 该 ref 曾在模板、computed 或 watch 中被读取过,因此 Vue 已将其标记为某个视图块的依赖
缺一不可。比如定义了 const flag = ref(false) 却没在模板里用它,后续改 flag.value = true 也不会引发任何更新。










