在 Vue 3 中,reactive是一个核心的响应式 API,它的主要作用是将一个普通的JavaScript 对象或数组 转换为一个响应式代理对象。这意味着,当这个对象的属性值发生变化时,所有依赖该属性的地方(例如 Vue 模板、计算属性等)都会自动、同步地更新,从而实现数据与视图的绑定
1.2 用法
这里还没有到effect,就简单介绍一下 reactive 的代理功能
import { reactive } from 'vue'
const original = { foo: 1 }
const observed = reactive(original)
function fun(val){
console.log(val)
}
// get 访问
fun(observed.foo)
function isEqual(num1, num2){
return num1 === num2
}
// set
observed.foo = 2
isEqual(observed.foo, original.foo) // true
1.3 源码解析
这里以用法中的代码为参照,讲一下代码关键部分和经过部分,其他的内容等待后续解锁(后面再具体去说)
当我们用法中代码执行的时候,首先会进入到reactive这个函数中,这里比较关键的部分在于 creatReactiveObject 与 mutableHandlers
位置:packages\reactivity\src\reactive.ts
// 可响应对象映射
export const reactiveMap: WeakMap<Target, any> = new WeakMap<Target, any>()
// 下面这个注释,是告诉打包工具这是一个无副作用函数
// 打包工具解析到这个注释可以进行更激进的"Tree shaking"
/*@__NO_SIDE_EFFECTS__*/
export function reactive(target: object) {
// ....
// 创建一个可响应的对象代理
return createReactiveObject(
target, // 目标对象
false, // 不是只读对象
mutableHandlers, // 可变代理处理函数
mutableCollectionHandlers, // 可变集合代理处理函数
reactiveMap, // 响应对象映射
)
}
可以看到,这里使用了 createReactiveObject 创建了一个响应式代理对象
位置:packages\reactivity\src\reactive.ts
enum TargetType {
INVALID = 0, // 无效目标类型
COMMON = 1, // 普通对象或数组
COLLECTION = 2, // 集合对象
}
function createReactiveObject(
target: Target, // 目标对象
isReadonly: boolean, // 是否只读对象
baseHandlers: ProxyHandler<any>, // 基础代理处理函数
collectionHandlers: ProxyHandler<any>, // 集合代理处理函数
proxyMap: WeakMap<Target, any>,// 弱引用映射,用于存储目标对象和对应的代理对象
) {
// 非对象直接返回
if (!isObject(target)) {
if (__DEV__) {
warn(
`value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(
target,
)}`,
)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
// 如果目标是一个响应式对象,且不是只读对象,直接返回目标
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// only specific value types can be observed.
// 只有特定的值类型才能被观察
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// target already has corresponding Proxy, return it.
// 如果目标已经有一个对应的代理,直接返回代理
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 创建代理
// 根据目标类型选择不同的代理处理函数
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
// 存储代理到弱引用映射中
proxyMap.set(target, proxy)
return proxy
}
// 目标类型映射函数
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON // 普通对象或数组
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION // 集合对象
default:
return TargetType.INVALID // 无效目标类型
}
}
// 获取目标对象的类型
function getTargetType(value: Target) {
// 跳过或者不可扩展的对象,返回无效类型,其他按照原始类型映射返回
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}
位置:packages\reactivity\src\baseHandlers.ts
export const mutableHandlers: ProxyHandler<object> =
/*@__PURE__*/ new MutableReactiveHandler()
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
// 是否只只读对象
protected readonly _isReadonly = false,
// 是否为浅响应式对象
protected readonly _isShallow = false,
) {}
/**
* 处理代理的 get 操作
* @param target 目标对象
* @param key 访问的属性名
* @param receiver 目标对象的原型或接收者对象
* @returns 访问结果
*/
get(target: Target, key: string | symbol, receiver: object): any {
// ...
// 处理数组属性
const targetIsArray = isArray(target)
// ...
// 映射目标对象的属性到代理对象
const res = Reflect.get(
target,
key,
receiver,
)
// ...
// 判断访问的key是否是对象,如果是对象,将key的对应的对象变成响应式
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
class MutableReactiveHandler extends BaseReactiveHandler {
constructor(isShallow = false) {
super(false, isShallow)
}
// 处理代理的 set 操作
set(
target: Record<string | symbol, unknown>,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
// ...
const result = Reflect.set(
target,
key,
value,
isRef(target) ? target : receiver,
)
// ...
return result
}
// 处理代理的 delete 操作
deleteProperty(
target: Record<string | symbol, unknown>,
key: string | symbol,
): boolean {
const hadKey = hasOwn(target, key)
const oldValue = target[key]
const result = Reflect.deleteProperty(target, key)
// ...
return result
}
// 处理代理的 has 操作
has(target: Record<string | symbol, unknown>, key: string | symbol): boolean {
const result = Reflect.has(target, key)
// ...
return result
}
// 处理代理的 ownKeys 操作
ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[] {
// ...
return Reflect.ownKeys(target)
}
}
1.4 运行流程(调试流程)
1.4.1 编写调试代码,打断点
1.4.2 创建可响应式的对象
1.4.3 到达createReactiveObject函数内部,创建代理对象并返回
1.4.4 访问代理对象内部,触发get,映射目标对象的属性到代理对象,并返回

1.4.5 为代理对象赋值触发set,设置属性值

1.5 问题
1.5.1 为什么要使用Proxy 与 Reflect
一、 为什么选择 Proxy 作为核心代理机制?
- 全面的拦截能力与动态属性支持
**Proxy 可以拦截对一个对象的几乎所有基本操作,包括属性读取 (get)、设置 (set)、删除 (deleteProperty)、检查存在性 (has)、枚举 (ownKeys) 等。这意味着,无论是新增属性、删除属性,还是直接通过索引修改数组 (arr[0] = value),Proxy 都能自动感知并触发更新。**这彻底解决了 Vue 2 中必须依赖 Vue.set 和 Vue.delete 等特殊 API 来"打补丁"的尴尬局面,使得开发者可以像操作普通 JavaScript 对象一样自然地操作响应式数据,代码更加直观和简洁。
- 卓越的性能与按需响应
在 Vue 2 中,Object.defineProperty 必须在初始化时递归遍历对象的每一个已有属性并进行劫持,对于嵌套深、属性多的对象,初始化开销较大 。而 Proxy 是对整个对象进行一层代理,初始化时无需深度遍历,性能更优。更重要的是,Proxy 的 get 拦截是惰性的:只有在属性被实际访问时,才会进行依赖收集和可能的深层代理。这种"按需响应"的机制,使得 Vue 3 在处理大型对象和复杂嵌套结构时性能表现显著提升。
- 完美的数组响应式支持
Object.defineProperty 无法有效监听数组索引的直接赋值和 length 属性的变化。Vue 2 不得不通过重写数组的 7 个变异方法(如 push, pop)来曲线救国 ,但这仍无法覆盖所有场景。Proxy 则能原生拦截对数组的任何操作,包括通过索引修改、调用任何原生方法,甚至是修改 length,从而实现了真正完整、无需特殊处理的数组响应式。
二、 为什么必须配合 Reflect 使用?
尽管 Proxy 能力强大,但若单独使用,在复杂场景下会暴露出行为不一致和 this 指向错误等问题。
- 确保正确的 this 上下文指向(核心原因)
这是 Reflect 在 Vue 3 响应式中最关键的作用。在 Proxy 的拦截器(如 get)内部,如果直接返回 target[key],那么当 target 的属性是一个 getter 访问器时,getter 函数内部的 this 将指向原始对象 target,而非代理对象 proxy。这会导致依赖收集错位,响应式链路断裂。**Reflect.get(target, key, receiver) 方法的第三个参数 receiver,可以显式指定操作中的 this 值。Vue 3 在调用时传入代理对象本身作为 receiver,从而确保无论访问器、继承链如何复杂,this 始终指向正确的响应式代理,**保证了依赖能够被准确收集到代理对象上,这是响应式系统正确工作的基石。
- 提供标准化、无副作用的默认行为
Reflect 的方法与 Proxy 的陷阱(trap)一一对应,其设计目的就是为 Proxy 提供一套规范化的默认行为实现 。**在 Vue 3 的拦截器中,通常会先调用 Reflect 的对应方法执行原始操作,然后再添加响应式逻辑(如 track 或 trigger)。**这样做有两大好处:
行为一致性:保证了代理对象在完成响应式增强后,其基本操作行为(如赋值、删除的成功与否)与原始对象保持一致,符合 JavaScript 的语言规范。
健壮性 :**Reflect 方法(如 Reflect.set)会返回一个布尔值来明确指示操作是否成功,而不是像直接赋值那样可能抛出异常。**这使得 Vue 3 可以更安全、更统一地处理操作结果,例如只在 set 成功后才触发更新 (trigger)。
- 函数式风格与未来兼容性
**Reflect 的 API 采用函数式风格,比对应的操作符或 Object 上的方法更统一、更易于组合和抽象。这提升了 Vue 3 源码的可读性和可维护性。**同时,作为 ES6 标准的一部分,Reflect 与 Proxy 的结合代表了 JavaScript 元编程的未来方向,为框架的长期演进奠定了更好的基础
1.6 总结
reactive是 Vue 3 的核心响应式 API,主要作用是将普通 JavaScript 对象或数组转换为响应式代理对象。当对象属性变化时,所有依赖该属性的地方都会自动更新,实现数据与视图的绑定。