ref 的响应式本质是用 Object.defineProperty 拦截 .value 的 getter/setter:getter 读值、收集依赖并递归响应化对象;setter 判变、赋值并触发更新;它返回普通对象而非 Proxy,仅封装 value 属性。

ref 的响应式包装,本质是用 Object.defineProperty 对 .value 属性做 getter/setter 拦截,让每次读写都自动触发依赖收集和更新通知。它不改变原始值本身,而是在 ref 对象上“套一层壳”,把响应式逻辑藏在 .value 的访问过程里。
ref 包装的是 .value,不是原始值
你调用 ref(10),返回的不是数字 10,而是一个类似这样的对象:
- 内部存着私有字段
_value = 10 - 对外暴露一个
value属性,靠 getter 返回_value,setter 赋新值并触发更新 - 模板中写
{{ count }}时,Vue 自动解包,实际访问的就是这个valuegetter
getter 做了三件事:读值 + 收集依赖 + 自动响应化
当你读 myRef.value 时,getter 被触发,执行流程是:
- 调用
track(myRef, 'value'),把当前正在运行的副作用(比如组件 render 函数)记进myRef的依赖集合里 - 返回
this._value - 如果返回值是对象,还会递归调用
reactive()包装——所以ref({ x: 1 })的.value.x也是响应式的
setter 做了两件事:判变 + 写入 + 通知
当你赋值 myRef.value = 20,setter 被触发,执行流程是:
- 先用
Object.is(oldValue, newValue)判断是否真变了(避免无意义更新) - 若变化,更新
this._value = newValue - 立刻调用
trigger(myRef, 'value'),找出所有依赖myRef.value的 effect(组件、watch、computed),让它们重新执行
为什么不用 Proxy?因为 ref 是个普通对象
ref 返回的是一个带 value 属性的 JavaScript 对象,不是 Proxy。它选择 Object.defineProperty 是为了精确控制 .value 这一个属性的访问行为,轻量且兼容性好。而 reactive 需要拦截整个对象的所有操作,才必须用 Proxy。


















