前言
最近在学习 Vue 3 响应式系统,记录一下动手写的一个极简版 reactive 实现。这个实现没有额外添加任何功能,目的就是把 Vue 3 中 reactive 的核心设计思路用最简洁的方式抽出来。
设计思路
一个可靠的 reactive 函数必须解决以下几个关键问题:
-
如何判断一个对象是否已经被代理过? 如果用户多次对同一个对象调用 reactive,必须返回同一个代理对象,否则依赖关系会丢失,导致响应式失效。
-
如何避免重复创建 Proxy? 创建 Proxy 是有开销的,而且同一个原始对象只能对应一个代理实例。最好的方式是用一个缓存结构来记录“原始对象 → 代理对象”的映射。
-
用什么数据结构做缓存? 键必须是原始对象本身,且当原始对象不再被引用时,缓存应该自动清除 → 自然想到用 WeakMap。
-
如何快速判断一个对象是否是 reactive 创建的(实现 isReactive)? 最简单的办法是在 Proxy 的 get 拦截器里,专门处理一个“魔法 key”。当有人访问这个 key 时,直接返回 true,表示我是 reactive 对象。这样既不需要修改原始对象,又能在运行时快速判断。
-
非对象类型怎么处理? reactive 只针对对象生效,如果传入原始值,直接返回。
基于以上思路,设计核心就是:
- 用 WeakMap 缓存代理结果
- 用一个特殊的标志 key(__v_isReactive)在 get 中返回 true 来支持判断
- 在创建前先检查标志 + 缓存,双重保险防止重复代理
代码实现
第一步:定义标志位和 Proxy 处理函数
// 建立枚举,定义一个特殊的标志 key
export enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive'
}
// Proxy 的 get/set 拦截器
export const mutableHandlers: ProxyHandler<any> = {
/**
* @param target - 原始对象
* @param key - 访问的属性
* @param receiver - 代理对象本身(Proxy 实例)
*/
get(target, key, receiver){
// 当访问这个特殊 key 时,直接返回 true,这就是我们判断 isReactive 的依据
if(key === ReactiveFlags.IS_REACTIVE){
return true;
}
// 使用 Reflect.get 正确转发操作
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver){
// 使用 Reflect.set 正确设置值
return Reflect.set(target, key, value, receiver);
},
}
这里通过 get 拦截实现 isReactive 判断,并且这里必须使用Reflect,而不是return target[key]或者是receiver[key]
const obj = {
_name: 'Vue',
get name() {
return this._name.toUpperCase();
}
};
const proxy = new Proxy(obj, {
get(target, key, receiver) {
// return target[key]; // 错误方式,这导致访问proxy.name时,在this._name时,由于this指向了target,而没有指向proxy,所以proxy里的get无法拦截到_name的调用
// return receiver[key] // 错误方式,会导致无限进入get循环
return Reflect.get(target, key, receiver); // 正确方式
}
});
console.log(proxy.name); // 期望输出 "VUE"
第二步:实现 reactive 函数(reactive.ts)
import { isObject } from "@vue/shared"; // Vue 官方的工具函数,判断是否为对象(排除 null)
import { ReactiveFlags, mutableHandlers } from "./baseHandler";
// 用 WeakMap 缓存:原始对象 => 代理对象
// 弱引用,自动垃圾回收,不泄漏内存
const reactiveMap = new WeakMap()
// 对外暴露的 reactive 函数
export function reactive(target){
return createReactiveObject(target);
}
// 核心创建逻辑
function createReactiveObject(target){
// 步骤 1:如果不是对象,直接返回(当前返回 undefined)
if(!isObject(target)){
return;
}
// 步骤 2:如果目标对象已经带有标志,说明它已经被代理过了
// 注意:这里访问 target[flag] 会触发 Proxy 的 get,返回 true
// 即使传入的是 proxy 本身,也能正确识别
if(target[ReactiveFlags.IS_REACTIVE]){
return target;
}
// 步骤 3:检查缓存中是否已经存在这个原始对象的代理
if(reactiveMap.has(target)){
return reactiveMap.get(target);
}
// 步骤 4:真正创建 Proxy 代理
const proxy = new Proxy(target, mutableHandlers);
// 步骤 5:缓存起来,下次直接复用同一个 proxy
reactiveMap.set(target, proxy);
return proxy;
}
完整流程
- 调用 reactive(obj)
- 先判断 obj 是否是对象 → 不是则返回
- 访问 obj[__v_isReactive] → 如果是已代理对象,会触发 get 返回 true → 直接返回自身
- 检查 reactiveMap 是否已有缓存 → 有则直接返回
- 创建 new Proxy(obj, mutableHandlers)
- 把映射关系存入 reactiveMap
- 返回 proxy
测试效果
const raw = { count: 0 };
const proxy1 = reactive(raw);
const proxy2 = reactive(raw); // 缓存命中
const proxy3 = reactive(proxy1); // 标志位判断命中
console.log(proxy1 === proxy2); // true
console.log(proxy1 === proxy3); // true
console.log(proxy1[ReactiveFlags.IS_REACTIVE]); // true
实现了“同一个对象只代理一次,返回同一个代理实例”。
总结
- WeakMap 缓存避免重复代理
- 特殊标志 key + get 拦截实现 isReactive 判断
- 不污染原始对象
- 支持传入 proxy 时直接返回自身