Reactive 实现
Vue3 Proxy 代理初步实现
前面实现 ref 的响应式,我们封装了 link 方法收集依赖和 propagate 方法触发依赖,因此 reactive 的实现可以复用。
在Vue官方源码中, reactive 都是接收一个对象,通过 proxy 代理对象,当访问对象的属性时,触发 get 代理,当修改对象的属性时,触发 set 代理。
在 get 方法调用 link 方法收集依赖,并使用 Reflect.get 映射返回值;在 set 方法中使用 Reflect.set 保存新值,调用 propagate 方法触发依赖,最后返回新值。
import { link, propagate } from './system.ts'
import { activeSub } from './effect.ts'
import { isObject } from '@vue/shared'
export const reactive = (obj) => {
return createObjectReactive(obj)
}
export function createObjectReactive(target) {
/**
* reactive 必须接受一个对象
*/
if (!isObject(target)) {
/**
* target 不是一个对象,哪儿来的回哪儿去
*/
return target
}
const proxy = new Proxy(target, {
get(target, key) {
console.log('target, key', target, key)
// 收集依赖,绑定 target 中某一个 key 和 sub 之间的关系
trackReactive(target, key)
// 返回值
return Reflect.get(target, key)
},
set(target, key, newValue) {
console.log('target, key, newValue', target, key, newValue)
// 保存新值,set 的时候,通知之前收集的依赖,重新执行
const res = Reflect.set(target, key, newValue)
// 触发依赖
triggerReactive(target, key, res)
return res
},
})
return proxy
}
/**
* 收集依赖
* @param target 目标对象
* @param key 属性名
*/
function trackReactive(target, key) {
link(dep, activeSub)
}
/**
* 触发依赖
* @param target 目标对象
* @param key 属性名
*/
function triggerReactive(target, key) {
propagate(target, key)
}而 link 方法需要参数 dep 和 sub ,sub 可以引入 activeSub ,而 dep 是一个包含 subs 和 subsTail 的类实例对象。现在就是要思考怎么实现。
既然它需要一个包含 subs 和 subsTail 的类实例对象,那么我们可以创建一个类 Dep,并添加 subs 和 subsTail 两个属性。后续把这个类实例对象传给 link 方法生成节点绑定到链表上就好了。现在需要思考用什么形式的数据格式,来关联保存 reactive 对象的属性。
在Vue 官方源码中, reactive 会先创建一个 WeakMap 保存所有对象,值是一个对象;每一个对象是通过 Map 保存的,每一个属性值则是 new Dep 类实例对象。格式如下图所示:

const obj = reactive({
a: 1,
b: 2,
c: 3
})
// 转换的格式:
[WeakMap]: {
[obj]: {
[a]: new Dep,
[b]: new Dep,
[c]: new Dep
}
}在 trackReactive 方法中,我们需要做的操作为:
- 根据
target获取WeakMap中保存的对象- 如果能拿到对象,说明之前有保存到,直接使用
- 如果拿不到对象,说明是第一次获取,
new Map新建一个Map,WeakMap.set()保存到WeakMap中
- 根据
key获取Map中保存数据- 如果能拿到数据,说明之前有保存到,直接使用
- 如果拿不到数据,说明是第一次获取,
new Dep新建一个Dep类实例,Map.set()保存到Map中
- 调用
link方法,把dep和activeSub传入
import { link, propagate, type Link } from './system.ts'
import { activeSub } from './effect.ts'
export const reactive = (obj) => {
return createObjectReactive(obj)
}
export function createObjectReactive(target) {
if (!isObject(target)) {
return target
}
const proxy = new Proxy(target, {
get(target, key) {
console.log('target, key', target, key)
// 收集依赖
trackReactive(target, key)
return Reflect.get(target, key)
},
set(target, key, newValue) {
console.log('target, key, newValue', target, key, newValue)
// 保存新值
const res = Reflect.set(target, key, newValue)
// 触发依赖
triggerReactive(target, key, res)
return res
},
})
return proxy
}
/**
* 绑定 target 的 key 关联的所有的 Dep
* obj = { a: 1, b: 2, c: 3 }
* targetMap = {
* [obj]: {
* [a]: new Dep,
* [b]: new Dep,
* [c]: new Dep
* }
* }
*/
const trackWeakMap = new WeakMap()
function trackReactive(target, key) {
if (activeSub) {
let depsMap = trackWeakMap.get(target)
/**
* 没有depsMap说明之前没有收集过这个对象的key,创建一个新的
*/
if (!depsMap) {
depsMap = new Map()
trackWeakMap.set(target, depsMap)
}
let dep = depsMap.get(key)
/**
* 没有dep说明之前没有收集过,创建一个新的
*/
if (!dep) {
dep = new Dep()
depsMap.set(key, dep)
}
link(dep, activeSub)
}
}
class Dep {
subs: Link
subsTail: Link
constructor() {}
} 而 propagate 方法需要参数 subs 链表,循环链表拿到对应依赖并执行。前面我们已经创建好了 WeakMap,并确立了数据结构,因此可以通过代理的对象和键名拿到对应的 Dep 对象,再通过 Dep 对象拿到 subs 链表,传参即可。
在 triggerReactive 方法中,我们需要做的操作为:
- 根据
target获取WeakMap中保存的对象,拿到对应的Map,如果拿不到,return阻止往下执行 - 根据
key获取Map中保存数据,拿到对应的Dep,如果拿不到,return阻止往下执行 - 调用
propagate方法,把dep.subs传入
function trackReactive(target, key) {
if (activeSub) {
let depsMap = trackWeakMap.get(target)
/**
* 没有depsMap说明之前没有收集过这个对象的key,创建一个新的
*/
if (!depsMap) {
depsMap = new Map()
trackWeakMap.set(target, depsMap)
}
let dep = depsMap.get(key)
/**
* 没有dep说明之前没有收集过,创建一个新的
*/
if (!dep) {
dep = new Dep()
depsMap.set(key, dep)
}
link(dep, activeSub)
}
}
function triggerReactive(target, key, res) {
const depsMap = trackWeakMap.get(target)
/**
* deosMap 没有,表示这个对象,没有任何属性在 sub 中访问
*/
if (!depsMap) return
const dep = depsMap.get(key)
/**
* dep 没有,表示这个 key 没有在 sub 中访问
*/
if (!dep) return
propagate(dep.subs)
} Vue3 this 指向代理对象
接下来看一个例子:
例子
import { ref, effect, reactive } from '../dist/reactivity.esm.js'
let state = reactive({
a: 1,
get count() {
return this.a
},
})
effect(() => {
console.log('state.a', state.count)
})
setTimeout(() => {
state.a = 2
}, 1000)对象 state 中的 count 属性是一个 getter,当 state.a 发生变化时,state.count 理应会变化, state.count 应该要触发 effect 函数。下面来看打印结果:

第一行是一开始执行 effect 回调函数运行到 stat.count 这行代码时,触发了 get 收集依赖.然后执行回调函数 console.log 打印 state.count 的值,此时 state.count 的值是 1。过了一秒后修改了 state.a 的值,触发了 set 。
触发 set 后理应重新执行依赖项 effect 回调函数,但是并没有,我们来看看 WeakMap 中有没有收集到它的依赖项。回到 trackReactive 函数,打印 target 和 key 看看:

可以看到 target 是 {a: 1} 的对象, key 是 count,因此 this 指向有问题,this 指向的是 {a: 1} 而不是代理对象,也就收集不到依赖了。
解决方法很简单,Proxy 的 get 方法第三个参数 recevier 指向的是代理对象,因此我们可以通过 receiver 来获取代理对象:
export function createObjectReactive(target) {
if (!isObject(target)) {
return target
}
const proxy = new Proxy(target, {
get(target, key) {
get(target, key, recevier) {
console.log('target, key', target, key);
// 收集依赖
trackReactive(target, key)
// 保证访问器里的对象指向代理对象
return Reflect.get(target, key)
return Reflect.get(target, key, recevier)
},
set(target, key, newValue, recevier) {
console.log('target, key, newValue', target, key, newValue);
// 触发依赖
const res = Reflect.set(target, key, newValue, recevier)
triggerReactive(target, key, res)
return res
}
})
return proxy
}现在访问器里的对象也能正常更新了:

Vue3 重复代理处理
再看一个例子:
例子
let obj = {
a: 1,
}
let state1 = reactive(obj)
let state2 = reactive(obj)
console.log('state1 === state2', state1 === state2)一个对象重复做代理,Vue官方打印结果是相等的,为 true,我们本地还没做处理,每次接收对象都直接 new Proxy 创建一个新的代理,因此 state1 和 state2 是不相等的,为 false。
解决方法很简单,我们创建一个新的 WeakMap 变量来存储代理对象,每次创建代理对象的时候,先去这个 WeakMap 里查找,如果找到了,直接返回,如果没找到,再创建新的代理对象,并存储到 WeakMap 里。
let reactiveMap = new WeakMap() // 复用同一个对象的代理
export function createObjectReactive(target) {
if (!isObject(target)) {
return target
}
let projectProxy = reactiveMap.get(obj)
// 如果已经代理过,直接返回
if (projectProxy) return projectProxy
const proxy = new Proxy(target, {
get(target, key, recevier) {
// 收集依赖
trackReactive(target, key)
// 保证访问器里的对象指向代理对象
return Reflect.get(target, key, recevier)
},
set(target, key, newValue, recevier) {
// 触发依赖
const res = Reflect.set(target, key, newValue, recevier)
triggerReactive(target, key, res)
return res
},
})
reactiveMap.set(obj, proxy) // 没代理过的对象,保存
return proxy
}不过不要小瞧用户的需求,他们可能还会把 reactive 代理过的对象作为参数,示例代码如下:
let obj = {
a: 1,
}
let state1 = reactive(obj)
let state2 = reactive(state1)
console.log('state1 === state2', state1 === state2)解决方法为新建一个变量 WeakSet ,每次代理对象的时候,都会 add 添加进去。下次再要创建代理对象的时候,先 has 判断 d 在不在 WeakSet 里,如果有说明之前已经代理过了,是一个响应式对象,直接拿出来复用,没有再 new Proxy 新建一个代理。
let reactiveMap = new WeakMap() // 复用同一个对象的代理
let reactiveSet = new WeakSet() // 保存已经代理过的对象
export function createObjectReactive(target) {
if (!isObject(target)) {
return target
}
if (isReactive(obj)) return obj // 如果已经代理过了,直接返回
let projectProxy = reactiveMap.get(obj)
// 如果已经代理过,直接返回
if (projectProxy) return projectProxy
const proxy = new Proxy(target, {
get(target, key, recevier) {
// 收集依赖
trackReactive(target, key)
// 保证访问器里的对象指向代理对象
return Reflect.get(target, key, recevier)
},
set(target, key, newValue, recevier) {
// 触发依赖
const res = Reflect.set(target, key, newValue, recevier)
triggerReactive(target, key, res)
return res
},
})
reactiveMap.set(obj, proxy)
reactiveSet.add(proxy) // 保存已经代理过的对象
return proxy
}
/**
* 判断是否收集过依赖
* @param obj 对象
*/
export function isReactive(obj) {
return reactiveSet.has(obj)
} 注意
这里 add 添加的是 proxy,而不是 obj,因为 obj 是原始对象,原始对象没有响应式,只有代理对象才有响应式。
Vue3 Ref 方法复用
之前在实现 ref 的功能时,我们创建了一个 RefImpl 类,在构造器中当时是直接把值保存到 this._value 上,现在实现了 reactive 方法后,可以判断 ref 接收的是不是一个对象,如果是,直接调用 reactive 方法实现响应式即可。
import { isObject } from '@vue/shared'
import { reactive } from './reactive'
class RefImpl {
_value
subs: Link
subsTail: Link;
[ReactiveFlags.IS_REF] = true
constructor(value) {
this._value = value
this._value = isObject(value) ? reactive(value) : value
}
get value() {
if (activeSub) {
trackRef(this)
}
return this._value
}
set value(newValue) {
this._value = newValue
this._value = isObject(newValue) ? reactive(newValue) : newValue
triggerRef(this)
}
}Vue3 优化 Proxy 对象参数反复创建
每次执行 new Proxy 时都要重新创建一个对象作为第二个参数,这里可以修改一个,把该对象提取出来,这样就可以复用这个对象了。
const mutableHandlers = {
get(target, key, recevier) {
// 收集依赖
trackReactive(target, key)
// 保证访问器里的对象指向代理对象
return Reflect.get(target, key, recevier)
},
set(target, key, newValue, recevier) {
// 触发依赖
const res = Reflect.set(target, key, newValue, recevier)
triggerReactive(target, key, res)
return res
},
}
export function createObjectReactive(target) {
if (!isObject(obj)) {
return obj
}
// 判断是不是reactive代理过的对象
if (isReactive(obj)) return obj
// 判断是不是同一个对象重复代理
let projectProxy = reactiveMap.get(obj)
// 如果已经代理过,直接返回
if (projectProxy) return projectProxy
const proxy = new Proxy(target, mutableHandlers)
reactiveMap.set(obj, proxy)
reactiveSet.add(proxy)
return proxy
}Vue3 新老属性值一样处理
下面再来看一个例子:
例子
let obj = {
a: 1,
}
let state1 = reactive(obj)
effect(() => {
console.count('effect' + state1.a)
})
setTimeout(() => {
state1.a = 1
}, 1000)这个例子中,state1.a 被赋值为 1,一秒钟之后,state1.a 再次被赋值为 1,前后两个值没有发生变化,按理来说应该不需要触发 effect 函数,但实际上却触发了 effect 函数,这是不合理的。
为了解决这个问题,我们需要在 set 函数中判断新值和旧值是否相等,如果相等,则不触发 effect 函数。步骤如下:
- 到
share/src/index.ts下,写一个公共方法hasChanged,接收两个值,新值和旧值,通过Object.is方法判断两个值是否相等,如果相等,则返回false,否则返回true。 - 在
reactivity/src/reactive.ts下,引入方法,在set方法中判断新旧两值是否相等,如果不等,才调用trigger方法触发依赖。 RefImpl类的set方法也如法炮制修改。
export function isObject(value) {
return value !== null && typeof value === 'object'
}
export function hasChanged(newValue, oldValue) {
return !Object.is(newValue, oldValue)
} import { isObject, hasChanged } from '@vue/shared'
// ...
const mutableHandlers = {
get(target, key, recevier) {
// 收集依赖
trackReactive(target, key)
// 保证访问器里的对象指向代理对象
return Reflect.get(target, key, recevier)
},
set(target, key, newValue, recevier) {
const oldValue = target[key]
// 触发依赖
const res = Reflect.set(target, key, newValue, recevier)
triggerReactive(target, key, res)
// 新旧两值不同,才触发更新
if (hasChanged(newValue, oldValue)) triggerReactive(target, key, res)
return res
},
}import { isObject } from '@vue/shared'
import { isObject, hasChanged } from '@vue/shared'
// ...
class RefImpl {
_value
subs: Link
subsTail: Link;
[ReactiveFlags.IS_REF] = true
constructor(value) {
this._value = isObject(value) ? reactive(value) : value
}
get value() {
if (activeSub) {
trackRef(this)
}
return this._value
}
set value(newValue) {
// 如果新值和旧值相等,则不进行更新
if (!hasChanged(newValue, this._value)) return
this._value = isObject(newValue) ? reactive(newValue) : newValue
triggerRef(this)
}
}Vue3 Reactive 内使用 Ref
下面来看一个例子:
例子
const a = ref(1)
let state1 = reactive({
a,
})
effect(() => {
console.count('effect' + state1.a)
})如果在 reactive 对象内使用了 ref 创建的属性,Vue 官方内部会帮我们自动拿到 .value 的值,我们使用时无需 state1.a.value,而是直接 state1.a 即可。
想要实现这个功能,可以借助前面实现 RefImpl 时,创建的 isRef 方法,只要是 ref 变量,他都有一个 __v_isRef 属性,因此我们可以借助这个方法很轻松就能判断当前的值是不是 ref ,如果是返回 .value ,不是返回自身。
在 Proxy 的 get 方法内做判断,因为获取值都会触发 get 方法,get 方法返回啥值,获取到的值就是啥。
import { isRef } from './ref.ts'
// ...
const mutableHandlers = {
get(target, key, recevier) {
// 收集依赖
trackReactive(target, key)
// 保证访问器里的对象指向代理对象
return Reflect.get(target, key, recevier)
const res = Reflect.get(target, key, recevier)
// 如果访问的是ref对象,则返回.value,否则返回本身
return isRef(res) ? res.value : res
},
set(target, key, newValue, recevier) {
const oldValue = target[key]
// 触发依赖
const res = Reflect.set(target, key, newValue, recevier)
if (hasChanged(newValue, oldValue)) triggerReactive(target, key, res)
return res
},
}如果不使用 .value 修改 reactive 内的 ref 的值,也应该同步修改 ref 的值,示例代码如下:
例子
const a = ref(1)
let state1 = reactive({
a,
})
effect(() => {
console.count('effect' + state1.a)
})
setTimeout(() => {
state1.a = 2
state1.a = ref(3)
}, 1000)找到 Proxy 的 set 方法函数,判断 oldValue 是不是一个 ref 对象。如果是,且新值 newValue 不是一个 ref ,则同步修改。
const mutableHandlers = {
get(target, key, recevier) {
// 收集依赖
trackReactive(target, key)
// 保证访问器里的对象指向代理对象
const res = Reflect.get(target, key, recevier)
// 如果访问的是ref对象,则返回.value,否则返回本身
return isRef(res) ? res.value : res
},
set(target, key, newValue, recevier) {
const oldValue = target[key]
// 如果新值是ref对象,则直接赋值
if (isRef(oldValue) && !isRef(newValue)) {
oldValue.value = newValue
// 如果值是ref,更新后会自动触发sub的更新,所以不需要执行后续代码
return res
}
// 触发依赖
const res = Reflect.set(target, key, newValue, recevier)
if (hasChanged(newValue, oldValue)) triggerReactive(target, key, res)
return res
},
}Vue3 代码转移
reactive.ts 内部抽离出公共方法或者属性,比如 trackWeakMap、trackReactive、triggerReactive、Dep 等,放到同级目录新建的 dep.ts 中;提取出来的 mutableHandlers 对象放到同级目录新建的 baseHandlers.ts 中,保证模块的高内聚低耦合。
import { isObject, hasChanged } from '@vue/shared'
import { mutableHandlers } from './baseHandlers.ts'
export const reactive = (obj) => {
return createObjectReactive(obj)
}
let reactiveMap = new WeakMap() // 复用同一个对象的代理
let reactiveSet = new WeakSet()
export function createObjectReactive(target) {
/**
* reactive 必须接受一个对象
*/
if (!isObject(obj)) {
/**
* obj 不是一个对象,哪儿来的回哪儿去
*/
return obj
}
// 判断是不是reactive代理过的对象
if (isReactive(obj)) return obj
// 判断是不是同一个对象重复代理
let projectProxy = reactiveMap.get(obj)
// 如果已经代理过,直接返回
if (projectProxy) return projectProxy
const proxy = new Proxy(target, mutableHandlers)
reactiveMap.set(obj, proxy) // 没代理过的对象,保存
reactiveSet.add(proxy) // 保存已经代理过的对象
return proxy
}
/**
* 判断是否收集过依赖
* @param obj 对象
*/
export function isReactive(obj) {
return reactiveSet.has(obj)
}import { activeSub } from './effect.ts'
import { link, propagate } from './system.ts'
const trackWeakMap = new WeakMap()
export function trackReactive(target, key) {
if (activeSub) {
let depsMap = trackWeakMap.get(target)
/**
* 没有depsMap说明之前没有收集过这个对象的key,创建一个新的
*/
if (!depsMap) {
depsMap = new Map()
trackWeakMap.set(target, depsMap)
}
let dep = depsMap.get(key)
/**
* 没有dep说明之前没有收集过,创建一个新的
*/
if (!dep) {
dep = new Dep()
depsMap.set(key, dep)
}
link(dep, activeSub)
}
}
export function triggerReactive(target, key, res) {
const depsMap = trackWeakMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (!dep) return
propagate(dep.subs)
}
class Dep {
subs
subsTail
constructor() {}
}import { trackReactive, triggerReactive } from './dep.ts'
import { isRef } from './ref.ts'
export const mutableHandlers = {
get(target, key, recevier) {
// 收集依赖
trackReactive(target, key)
// 保证访问器里的对象指向代理对象
const res = Reflect.get(target, key, recevier)
// 如果访问的是ref对象,则返回.value,否则返回本身
return isRef(res) ? res.value : res
},
set(target, key, newValue, recevier) {
const oldValue = target[key]
// 如果新值是ref对象,则直接赋值
if (isRef(oldValue) && !isRef(newValue)) {
oldValue.value = newValue
// 如果值是ref,更新后会自动触发sub的更新,所以不需要执行后续代码
return res
}
// 触发依赖
const res = Reflect.set(target, key, newValue, recevier)
// 如果新值和旧值不同,则触发更新
if (hasChanged(newValue, oldValue)) triggerReactive(target, key, res)
return res
},
}Vue3 对象嵌套不更新
下面来看一个例子:
例子
let state1 = reactive({
a: {
b: 1,
},
})
effect(() => {
console.count('effect' + state1.a.b)
})
setTimeout(() => {
state1.a.b = 2
}, 1000)如果是对象嵌套的情况,修改对象内的对象的属性,不会触发更新,因为嵌套的对象没有收集依赖。
可能会有这样的疑问,为什么嵌套的对象没有收集依赖呢?不是被 Proxy 代理了么?这是因为 Proxy 代理的是最外层的对象,不包含嵌套的对象。可以打一个断点查看情况。
- 运行了
effect回调函数后,触发get方法 - 调用
trackReactive方法,收集依赖 - 映射该对象
- 判断它是不是
ref,不是,返回原值,即一个普通的object对象
由上可以看出,解决方法是不要给它返回普通对象,而是做一个判断,如果是对象,那么就用 reactive 包裹一下,这样嵌套的对象也会被代理,从而收集依赖。
import { isObject } from '@vue/shared'
import { trackReactive, triggerReactive } from './dep.ts'
import { isRef } from './ref.ts'
import { reactive } from './reactive.ts'
export const mutableHandlers = {
get(target, key, recevier) {
// 收集依赖
trackReactive(target, key)
// 保证访问器里的对象指向代理对象
const res = Reflect.get(target, key, recevier)
// 如果访问的是ref对象,则返回.value,否则返回本身
if (isRef(res)) {
return res.value
}
// 如果是对象,则返回代理对象
if (isObject(res)) {
return reactive(res)
}
return res
},
set(target, key, newValue, recevier) {
const oldValue = target[key]
// 如果新值是ref对象,则直接赋值
if (isRef(oldValue) && !isRef(newValue)) {
oldValue.value = newValue
// 如果值是ref,更新后会自动触发sub的更新,所以不需要执行后续代码
return res
}
// 触发依赖
const res = Reflect.set(target, key, newValue, recevier)
// 如果新值和旧值不同,则触发更新
if (hasChanged(newValue, oldValue)) triggerReactive(target, key, res)
return res
},
}Vue3 总结
Vue3 内容总结
Proxy代理与依赖管理
reactive 通过 Proxy 代理对象,在 get 中调用 trackReactive 收集依赖,在 set 中调用 triggerReactive 触发更新。依赖关系通过WeakMap(对象→Map)和 Map(属性→Dep 实例)的嵌套结构管理,每个 Dep 实例维护一个订阅者链表。
this指向与递归代理
使用 Reflect.get/set 的 receiver 参数确保访问器中的 this 指向代理对象。对于嵌套对象,在 get 中递归调用 reactive 实现深层代理,保证嵌套属性的响应性。
- 重复代理与缓存机制
通过 WeakMap 缓存原始对象到代理对象的映射,避免重复代理。WeakSet 标记已代理对象,防止代理对象再次被处理。
Ref与Reactive集成
ref 的值若为对象,自动用 reactive 包装。在 Proxy 的 get 中自动解包 ref 的 .value,set 中处理对 ref 属性的赋值,确保同步更新。
- 性能优化
hasChanged 函数通过 Object.is 比较新旧值,避免无变化时的冗余更新。mutableHandlers 抽离复用,提升代码结构清晰度。
Vue3 时序图
