vue3中ref需加.value是因为proxy无法代理原始值,必须用{value:xxx}对象包装后代理;其内部含_rawvalue存原始值、_value存响应式值,并通过__v_isref标识和getter/setter实现依赖追踪与更新。

“原始值转换”逻辑,指的是 Vue 3 为让 JavaScript 基本类型(String、Number、Boolean、BigInt、Symbol、null、undefined)具备响应式能力,所采用的一套包装 + 代理机制。它不是直接让原始值响应,而是用一个对象把它“包起来”,再对这个对象做响应式处理。
为什么必须包装?
因为 ES6 的 Proxy 只能代理对象,无法拦截原始值的读写。比如你不能写 new Proxy(42, handler)——这会直接报错。所以 Vue 必须把 42 变成类似 { value: 42 } 这样的结构,才能用 Proxy 拦截 .value 的访问和赋值。
ref 的封装结构长什么样?
简化来看,ref 返回的对象本质是一个带 value 属性的响应式对象,并附带标识:
- 有
__v_isRef: true属性,用于运行时快速识别 ref(如isRef()就靠它) -
value是 getter/setter,不是普通属性:读取时触发依赖收集(trackRefValue),赋值时触发更新通知(triggerRefValue) - 内部维护两个字段:
_rawValue(存原始输入值,用于变化比对),_value(存最终响应式值,基本类型就等于 _rawValue,对象则转为 reactive)
对不同类型的实际处理差异
ref 并不区分“只给基本类型用”,它统一接收任意值,但内部逻辑会智能分流:
- 传入
ref('hello')→_rawValue = 'hello',_value = 'hello'(字符串原样保留,无 reactive) - 传入
ref({ count: 1 })→_rawValue = { count: 1 },_value = reactive({ count: 1 })(自动调用toReactive转为响应式对象) - 传入已有的 ref(如
ref(refObj))→ 直接返回原 ref,避免嵌套包装
模板中为何不用 .value?
这是 Vue 编译器做的“自动脱 ref”(auto-unref):在模板里写 {{ count }},编译后实际是 {{ unref(count) }}。而 unref 的逻辑很简单:isRef(x) ? x.value : x。这个机制只在模板和 setup() 的返回上下文中生效,JS 逻辑中仍需显式写 .value。










