ref的本质是将值封装进带value属性的响应式“盒子”,因proxy无法代理原始值,必须包装成对象才能拦截读写以实现依赖收集与更新触发。

ref 的本质,是把一个值放进一个带 value 属性的响应式“盒子”里。这个盒子不是普通对象,而是一个精心设计的封装结构,让 JavaScript 原始值(string、number、boolean 等)也能被 Vue 追踪变化。
为什么必须包装?Proxy 拦不住原始值
ES6 的 Proxy 只能代理对象,不能代理字符串、数字这类原始值。你写 new Proxy(42, handler) 会直接报错。所以 Vue 必须把 42 变成类似 { value: 42 } 的对象,再对这个对象做响应式处理——这就是“包装”的根本原因。
- 不包装 → 原始值无法被 Proxy 拦截 → 无法触发依赖收集和更新通知
- 包装后 →
value成为可拦截的属性 → 读取时收集依赖,赋值时触发更新 - 内部还存一份
_rawValue,用于精确比对是否真的变了(比如从0改成0就不触发更新)
包装后的结构长什么样?
ref 返回的对象看起来像 { value: xxx },但 value 不是普通属性,而是 getter/setter:
递归分析 Vue 项目组件依赖,从入口文件生成组件层级图,支持 Vue 2/3,输出组件名、文件路径和属性。适用于分析组件结构、排查依赖或了解项目架构。
- 读
ref.value→ 触发trackRefValue,把当前副作用函数记为依赖 - 写
ref.value = newV→ 触发triggerRefValue,通知所有依赖重新执行 - 对象上还有
__v_isRef: true,供isRef()等工具函数快速识别 - 传入对象时(如
ref({a:1})),_value会自动调用reactive转为 Proxy,但整个 ref 结构不变
模板里为啥不用 .value?编译器帮你解了
你在模板里写 {{ count }},Vue 编译器实际把它转成了 {{ unref(count) }}。而 unref 的逻辑很简单:
- 如果是 ref → 自动取
.value - 如果不是 ref → 直接返回原值
- 这个自动解包只在模板和
setup()返回的对象中生效,JS 逻辑里仍要写.value
ref 和 reactive 的分工很清晰
ref 不是“简化版 reactive”,而是解决不同问题的工具:
-
ref:适合单个值、基础类型、需要整体替换的场景(比如user.value = newUser) -
reactive:适合多字段对象,改某个属性就触发对应更新(比如form.username = 'xxx') - ref 包对象时,底层其实是
reactive({ value: obj }),但对外统一暴露.value接口









