ref的响应式不依赖proxy,而是通过refimpl类结合getter/setter实现:读写.value时触发依赖收集与更新,基本类型直接存储,对象则委托reactive处理,模板中自动解包。

ref 的响应式不是靠 Proxy 实现的,而是靠一个叫 RefImpl 的类,配合 JavaScript 原生的 getter/setter 拦截对 .value 的读写操作。
RefImpl 是 ref 的真实身份
每次调用 ref(123),Vue 内部其实 new 了一个 RefImpl 实例。这个实例不是普通对象,它自带两个关键私有属性:
-
_rawValue:存原始值(比如数字 123、字符串 "hello") -
_value:存“加工后”的值——基本类型就直接是原值;如果是对象或数组,会自动调用reactive()转成 Proxy 响应式对象
也就是说:ref 对基本类型用 getter/setter,对嵌套对象则悄悄委托给 reactive + Proxy,不是非此即彼,而是分层协作。
getter/setter 怎么拦截 .value?
RefImpl 类里定义了 get value() 和 set value(newVal),它们不是普通方法,而是属性访问器:
- 读
myRef.value→ 触发get value()→ 先执行trackRefValue(this)收集当前副作用(比如effect或watch)作为依赖 - 写
myRef.value = 456→ 触发set value(456)→ 先比对新旧值(hasChanged),再更新_rawValue和_value,最后调用triggerRefValue(this)通知所有依赖更新
模板中能直接写 {{ count }} 而不用 {{ count.value }},正是因为 Vue 编译器在解析时做了自动解包——它识别出这是 ref,就帮你省掉 .value。
为什么不用 Proxy 直接代理 ref?
因为 Proxy 只能代理对象,不能代理原始值(string/number/boolean)。而 ref 的核心价值之一,就是让基本类型也能参与响应式系统。如果强行用 Proxy 包一层,会带来额外对象开销和语义混乱(比如 typeof ref(5) 变成 "object")。用 class + getter/setter 更轻量、更可控,也符合“基本类型走存取器、复杂类型交由 reactive”的分治设计。
依赖收集和触发更新怎么关联?
每个 RefImpl 实例都有一个 dep 属性(类型是 Set),专门存依赖它的副作用函数。getter 中的 trackRefValue(this) 就是把当前正在运行的 effect 加进这个 set;setter 中的 triggerRefValue(this) 就是遍历这个 set,逐个执行它们。整个过程不依赖全局状态,每个 ref 管自己的依赖,干净又可预测。











