必须用 .value 是因为 ref 返回的是响应式包装对象,其内部通过 getter/setter 拦截对 .value 的读写以实现依赖收集和更新触发;模板中自动解包,script 中需手动访问。

ref 为什么必须用 .value?本质是“包装器”设计
ref 不是直接返回原始值,而是返回一个响应式包装对象,这个对象内部用 .value 存放真实数据。Vue 需要通过这个属性拦截读写——在 getter 中收集依赖,在 setter 中触发更新。没有 .value 这一层,Vue 就无法追踪变化。
模板里不用 .value,是因为 Vue 做了自动解包
模板编译时,Vue 会识别 ref 类型变量,并自动访问其 .value。所以你在 {{ count }} 里写的是 ref 变量名,实际运行时等价于 {{ count.value }}。这不是语法糖,而是编译阶段的智能处理。
- 仅限模板中自动解包;在 script 中必须显式写 .value
- 解包只发生在顶层 ref 变量,嵌套对象属性(如
user.name)不会被解包 - 如果把 ref 赋给普通变量(如
const c = count),解包逻辑就失效,后续修改不会响应
对比 reactive:为什么基本类型非得用 ref?
JavaScript 的基本类型(string、number、boolean)无法被 Proxy 直接代理,而 reactive 底层依赖 Proxy。所以 Vue 用 ref 绕过这个限制:
递归分析 Vue 项目组件依赖,从入口文件生成组件层级图,支持 Vue 2/3,输出组件名、文件路径和属性。适用于分析组件结构、排查依赖或了解项目架构。
-
reactive({ count: 0 })→ 可以响应,因为是对象 -
reactive(0)→ 报错,Proxy 不能代理原始值 -
ref(0)→ 成功,因为返回的是 { value: 0 } 对象,Proxy 可代理该对象
手把手看底层做了什么
简化版逻辑如下(非源码直抄,但反映核心机制):
function ref(raw) {
const refObject = {
_raw: raw,
__v_isRef: true,
get value() {
track(refObject, 'value') // 收集依赖
return refObject._raw
},
set value(newVal) {
refObject._raw = newVal
trigger(refObject, 'value') // 触发更新
}
}
return refObject
}
正是这个 getter/setter,让 .value 成为响应式入口。你每次读写它,Vue 都能感知。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










