Vue模板中ref自动解包为.value,编译时静态识别并替换;script中需手动写.value,因JS无编译干预。reactive内ref属性、watchEffect、v-for遍历ref数组也自动解包,但普通对象/数组存ref、解构赋值、嵌套路径访问时不生效。

因为 Vue 在模板编译阶段就识别出 ref 变量,并自动插入 .value 访问逻辑;而 script 是纯 JavaScript 运行环境,没有这层编译干预,必须手动取值才能拿到响应式包裹里的真实数据。
模板里自动解包:编译时就“悄悄加了 .value”
Vue 模板不是直接执行 JS 表达式,而是先经过编译器处理。当它扫描到 {{ count }} 或 :title="count" 这类写法时,会静态判断 count 是否为 ref 类型。如果是,就直接在生成的渲染函数里替换成 count.value —— 你没写,但它已经帮你写了。
- 这个过程发生在构建或开发服务器启动时(非运行时),零性能损耗
- 支持场景包括:
{{ }}、v-bind、v-model、v-if、v-show、v-for中的遍历项 - 注意:它只对“顶层绑定”的 ref 生效,比如
{{ count }}可以,但{{ user.count }}(user 是 reactive 对象)就不行
script 里必须写 .value:JS 没有魔法
setup 函数、watch 回调、computed 函数这些都运行在标准 JavaScript 环境中。ref 就是一个普通对象,结构是 { value: xxx }。不写 .value,你就只是在读这个对象本身,而不是它包裹的值。
-
count是一个 ref 对象,类型是Ref<number></number> -
count.value才是真正的数字,也才是响应式系统真正追踪和更新的部分 - 漏写
.value是常见错误,比如在watch(count, ...)中传错参数,或在computed(() => count + 1)中得到[object Object]1
哪些地方也会自动解包?不止是模板
除了模板,Vue 在几个特定响应式上下文中也做了类似优化,目的都是减少心智负担:
-
reactive 对象内部的 ref 属性:如
const state = reactive({ count }),访问state.count直接得数值,等价于count.value -
watchEffect 和 watch 的依赖收集过程:写
watchEffect(() => console.log(count)),Vue 内部会自动读取count.value并建立响应联系 -
v-for 遍历 ref 包裹的数组:如
const list = ref([1,2,3]),v-for="item in list"中的item就是解包后的元素,不是 ref 对象
什么时候不会自动解包?容易踩坑的边界
自动解包不是无条件的,一旦脱离 Vue 明确识别的上下文,.value 就必须显式出现:
-
普通对象里存 ref:如
const obj = { count },那么obj.count就是 ref 本体,必须obj.count.value -
数组里直接放 ref:如
const arr = [ref(1), ref(2)],arr[0]不会解包,模板中写{{ arr[0] }}显示的是整个 ref 对象 -
解构赋值后丢失响应式识别:如
const { count } = toRefs(state)后可用,但const c = count后再用c,就只是个普通变量,不再受自动解包保护 -
嵌套路径访问:如
const user = ref({ name: 'Alice' }),模板中不能写{{ user.name }},必须写{{ user.value.name }}或改用reactive

















