深入解析 Vue3 中 ref 与 reactive 的原理及区别(附源码)
在 Vue3 的 Composition API 中,ref和reactive是实现响应式数据的核心 API,二者都基于 ES6 的Proxy API,但在使用场景和实现逻辑上存在显著差异。本文将从底层原理出发,结合 Vue3 源码片段,详细拆解两者的实现机制,并梳理它们的核心区别,帮助开发者更精准地选择响应式方案。
一、响应式的底层基石:Proxy API
在分析ref和reactive之前,必须先明确 Vue3 响应式的底层依赖 ——Proxy。与 Vue2 的Object.defineProperty相比,Proxy能直接代理整个对象(包括数组),支持动态新增 / 删除属性,且能拦截更多操作(如delete、in等),这也是ref和reactive实现的核心基础。
Proxy的基本用法如下:
const target = { count: 0 }
const proxy = new Proxy(target, {
// 拦截属性读取
get(target, key) {
track(target, key) // 依赖收集:记录当前使用该属性的副作用函数
return target[key]
},
// 拦截属性修改
set(target, key, value) {
target[key] = value
trigger(target, key) // 触发更新:执行所有依赖该属性的副作用函数
}
})
其中,track(依赖收集)和trigger(触发更新)是 Vue3 响应式系统的核心逻辑,ref和reactive都会复用这两个函数,但在代理对象的处理上存在差异。
二、reactive 原理:直接代理对象
reactive的核心作用是将普通对象 / 数组转换为响应式对象,它直接对目标对象进行Proxy代理,不额外包装一层结构。
1. reactive 的源码核心(简化版)
// 存储已代理的对象,避免重复代理
const reactiveMap = new WeakMap()
function reactive(target) {
// 1. 若目标不是对象/数组,直接返回(reactive不支持基础类型)
if (typeof target !== 'object' || target === null) {
return target
}
// 2. 若目标已被代理,直接返回缓存的代理对象
const existingProxy = reactiveMap.get(target)
if (existingProxy) {
return existingProxy
}
// 3. 创建Proxy代理,拦截get/set等操作
const proxy = new Proxy(target, {
get(target, key, receiver) {
// 特殊处理:如果访问的是内置属性(如__v_isReactive),直接返回标识
if (key === '__v_isReactive') {
return true
}
// 依赖收集:记录当前副作用函数与target、key的关联
track(target, key)
// 递归代理:若属性值是对象,访问时自动转为响应式(深度代理)
const res = Reflect.get(target, key, receiver)
return reactive(res) // 关键:深度响应式的实现
},
set(target, key, value, receiver) {
// 先获取旧值,判断是否需要触发更新
const oldValue = Reflect.get(target, key, receiver)
const result = Reflect.set(target, key, value, receiver)
// 若值发生变化,触发依赖更新
if (oldValue !== value) {
trigger(target, key)
}
return result
},
// 拦截delete操作(支持删除属性后触发更新)
deleteProperty(target, key) {
const hadKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey) {
trigger(target, key) // 删除属性后触发更新
}
return result
}
})
// 4. 缓存代理对象,存入WeakMap
reactiveMap.set(target, proxy)
return proxy
}
2. reactive 的核心特性
-
仅支持对象 / 数组:对基础类型(number、string 等)无效,传入基础类型会直接返回原值;
-
深度代理:访问对象的嵌套属性时,会自动对嵌套对象执行
reactive,实现深度响应式; -
直接操作属性:使用时无需额外加
.value,如state.count++; -
弱引用缓存:通过
WeakMap存储已代理对象,避免内存泄漏(目标对象被销毁时,缓存自动清除)。
三、ref 原理:包装基础类型的响应式容器
ref的核心作用是为基础类型(如 number、string)提供响应式支持,它通过一个 “容器对象” 包装基础类型,再对容器对象进行Proxy代理,间接实现基础类型的响应式。
1. ref 的源码核心(简化版)
// 存储ref对象的原始值和响应式标识
class RefImpl {
constructor(rawValue) {
// 1. 存储原始值(若原始值是对象,先转为reactive)
this._rawValue = rawValue
this._value = reactive(rawValue) // 关键:对象类型的ref会复用reactive
// 2. 标识当前是ref对象
this.__v_isRef = true
}
// 3. 通过getter拦截.value的读取,触发依赖收集
get value() {
track(this, 'value') // 依赖收集:关联副作用函数与ref的value属性
return this._value
}
// 4. 通过setter拦截.value的修改,触发更新
set value(newValue) {
// 若新值与原始值不同,更新原始值和响应式值
if (newValue !== this._rawValue) {
this._rawValue = newValue
this._value = reactive(newValue) // 新值是对象时,转为reactive
trigger(this, 'value') // 触发依赖更新
}
}
}
// ref函数:创建RefImpl实例
function ref(rawValue) {
return new RefImpl(rawValue)
}
// 辅助函数:判断是否为ref对象
function isRef(r) {
return !!(r && r.__v_isRef === true)
}
2. ref 的核心特性
-
支持所有类型:既可以包装基础类型(number、string),也可以包装对象 / 数组(内部会转为
reactive); -
需通过.value 访问:由于基础类型无法被
Proxy直接代理,需通过容器的value属性间接操作; -
自动解包场景:
-
在模板中使用时,无需写
.value(Vue 会自动解包),如<div>{{ count }}</div>; -
在
reactive对象中嵌套 ref 时,访问时会自动解包,如const state = reactive({ count: ref(0) }),使用时可直接state.count++。
-
四、ref 与 reactive 的核心区别
通过原理分析,可从以下 5 个维度清晰区分两者:
| 对比维度 | reactive | ref |
|---|---|---|
| 支持数据类型 | 仅对象 / 数组(引用类型) | 所有类型(基础类型 + 引用类型) |
| 访问方式 | 直接访问属性(如state.count) |
需通过.value(如count.value) |
| 深度响应式实现 | 访问嵌套属性时递归代理 | 包装对象后,内部调用 reactive 实现 |
| 自动解包场景 | 无自动解包需求 | 模板中、reactive 嵌套时自动解包 |
| 使用场景 | 管理复杂对象 / 数组(如表单数据) | 管理基础类型(如单个数字、字符串) |
典型使用场景示例
- reactive 场景:管理表单数据(复杂对象)
const form = reactive({
username: '',
password: ''
})
// 直接操作属性,无需.value
form.username = 'vue-dev'
- ref 场景:管理单个基础类型值(如计数器)
const count = ref(0)
// 需通过.value修改
count.value++
// 模板中自动解包:<div>{{ count }}</div>
五、常见疑问解答
-
为什么 ref 包装对象时,内部要转为 reactive?
因为对象本身是引用类型,直接代理容器的
value属性无法实现对象内部属性的响应式,所以 ref 内部会调用reactive,将对象转为响应式,确保嵌套属性也能触发更新。 -
什么时候该用 ref,什么时候该用 reactive?
-
若变量是基础类型(如 number、string),直接用 ref;
-
若变量是复杂对象 / 数组(如表单、列表),优先用 reactive;
-
若不确定变量类型,或后续可能从基础类型转为对象,用 ref 更灵活。
-
ref 的.value 会不会忘记写?
开发中可通过 ESLint 插件(如
eslint-plugin-vue)配置规则,检测到 ref 未用.value时自动提示,避免遗漏。
总结
ref和reactive都是 Vue3 响应式系统的核心 API,底层均基于Proxy和依赖收集机制,但设计思路不同:
-
reactive是 “直接代理”,专为引用类型设计,使用更简洁; -
ref是 “容器包装”,通过.value间接实现基础类型的响应式,场景更通用。
理解两者的原理和区别后,可根据具体数据类型和使用场景灵活选择,让响应式代码更高效、更易维护。
更多推荐


所有评论(0)