本文为原创文章,未获授权禁止转载,侵权必究!
本篇是 Vue3 源码解析系列第 2 篇,关注专栏
前言
我们知道 Vue3 中声明响应式是通过 reactive 和 ref 这两个函数,下面我们通过案例先来看下 reactive 是如何实现的。
案例
首先引入 reactive 和 effect 两个函数,之后声明 obj 响应式对象,接着又执行 effect 函数,该函数传入了一个匿名函数,最后两秒后又修改 obj.name 值。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../../../dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { reactive, effect } = Vue
const obj = reactive({
name: 'jc',
age: 18
})
effect(() => {
document.querySelector('#app').innerHTML = obj.name
})
setTimeout(() => {
obj.name = 'cc'
}, 2000)
</script>
</body>
</html>
reactive 实现
reactive 函数在 packages/reactivity/src/reactive.ts 文件下:
ts
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
reactive 函数实际执行的是 createReactiveObject 方法,而 target 参数就是我们传进来的对象。接着我们再看下 createReactiveObject 函数,该函数也在 packages/reactivity/src/reactive.ts 文件下:
ts
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// 省略
// target already has corresponding Proxy
// 缓存中读取 存在则直接返回
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 省略
// 这里的 baseHandlers 参数 就是传进来的 mutableHandlers
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 设置缓存
proxyMap.set(target, proxy)
// 返回 proxy 实例对象
return proxy
}
createReactiveObject 函数实际做了 proxyMap 缓存处理,最终返回一个 proxy 实例对象。这里我们主要关注 new Proxy 这段代码,第一个参数 target 为传进来的对象,即 { name: 'jc', age: 18 },第二个 baseHandlers 参数即传入的 mutableHandlers 对象,该对象定义在 packages/reactivity/src/baseHandlers.ts 中:
ts
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
该对象定义了 get 、 set 等方法,从而对传入的数据进行依赖收集和依赖触发,我们先看下结果,回头再对这块逻辑分析:

至此,reactive 函数执行完毕,obj 得到了一个 proxy 的实例对象。接着又执行 effect 方法,该方法定义在 packages/reactivity/src/effect.ts 文件中:
ts
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
// 创建 ReactiveEffect 实例
const _effect = new ReactiveEffect(fn)
// 省略
if (!options || !options.lazy) {
// 执行 ReactiveEffect 中的 run 方法
_effect.run()
}
// 省略
}
该函数先声明一个构造函数 ReactiveEffect 的实例对象 _effect,然后执行构造函数中的 run 方法。我们先看下 ReactiveEffect 构造函数:
ts
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
parent: ReactiveEffect | undefined = undefined
// 省略
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
run() {
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
this.parent = activeEffect
activeEffect = this
shouldTrack = true
// 省略
// 执行 fn 函数 即传入的匿名函数
// () => {
// document.querySelector('#app').innerHTML = obj.name
// }
return this.fn()
} finally {
// 省略
if (this.deferStop) {
this.stop()
}
}
}
stop() {
// stopped while running itself - defer the cleanup
if (activeEffect === this) {
this.deferStop = true
} else if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
这里接收一个 fn 方法即传入的匿名函数,然后设置 active、deps 等属性:

之后执行 _effect.run(),即执行构造函数 ReactiveEffect 的 run 方法。我们需要关注 activeEffect = this,此时被赋值为:

然后执行 fn 函数,即执行传入的匿名函数,之后执行 document.querySelector('#app').innerHTML = obj.name 触发 obj 的 get 方法。
get 方法上述中被定义在 packages/reactivity/src/baseHandlers.ts 文件中:
ts
const get = /*#__PURE__*/ createGetter()
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// 省略
const targetIsArray = isArray(target)
// 省略
// Reflect API
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
// Reflect.get() 等同于 res = target[key]
// Reflect 用来替代直接调用 Object 的方法
const res = Reflect.get(target, key, receiver)
// 省略
if (!isReadonly) {
// 核心,添加依赖收集
track(target, TrackOpTypes.GET, key)
}
if (shallow) {
return res
}
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
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
}
}
get 方法实际触发的是 createGetter 函数,我们主要关注 track(target, TrackOpTypes.GET, key) 这段代码,它是对数据的依赖收集,也是 get 方法的核心。 track 函数被定义在 packages/reactivity/src/effect.ts 文件中:
ts
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
let depsMap = targetMap.get(target)
if (!depsMap) {
// key: target 传入的对象 value: 创建 Map 对象
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
// key: 'name' value: Set 对象
depsMap.set(key, (dep = createDep()))
}
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
// dep 为 Set 对象
// eventInfo = { effect: activeEffect(之前已设置), target(传入的对象), type('get'), key('name') }
trackEffects(dep, eventInfo)
}
}
这里的 targetMap 为 WeakMap 对象,该对象是一个弱引用类型,那什么是弱引用类型呢?举个例子:
js
let obj = {
name: 'jc'
}
const map = new WeakMap() // new Map()
map.set(obj, 'cc')
obj = null
正常来说,对象为空,堆内存中数据没有指针指向就会被回收。但 map 数据依然存在,说明 Map 为强引用;设置 WeakMap,数据为空,则说明 WeakMap 为弱引用。准确地说,obj 不存在其他引用时, WeakMap 不会阻止垃圾回收,基于 obj 的引用将会被清除,这就证明 WeakMap 的弱引用特性。
回过来我们再看下 targetMap 对象,以传入的对象 { name: 'jc', age: 18} 作为 key,value 值为 Map 对象,之后设置 depsMap,key 当前为 name,value 为 Set 对象,具体可以看下 createDep 方法,最后执行 trackEffects(dep, eventInfo):
ts
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// 省略
if (shouldTrack) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
if (__DEV__ && activeEffect!.onTrack) {
activeEffect!.onTrack({
effect: activeEffect!,
...debuggerEventExtraInfo!
})
}
}
}
此时我们再看下 targetMap 对象数据:

这样就完成了数据的依赖收集,之后就可以通过指定对象指定属性获取到对应的 fn 方法 。而依赖收集本质上就是 targetMap 和 ReactiveEffect 之间的关联。
createGetter 执行完毕返回对应的值,当前为 jc:

两秒后执行 obj.name = 'cc',触发 set 方法,该方法定义在 packages/reactivity/src/baseHandlers.ts 中:
ts
const set = /*#__PURE__*/ createSetter()
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false
}
if (!shallow && !isReadonly(value)) {
if (!isShallow(value)) {
value = toRaw(value)
oldValue = toRaw(oldValue)
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// Reflect.set() 等同于 obj[key] = value
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
// 触发 getter
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 新值 旧值比较
// 依赖触发 核心
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
我们只需关注 trigger(target, TriggerOpTypes.SET, key, value, oldValue) 这行代码,也是依赖触发的核心。trigger 方法被定义在 packages/reactivity/src/effect.ts 文件中:
ts
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
// 根据传入的对象 获取 对应的 Map 对象
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
let deps: (Dep | undefined)[] = []
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
deps.push(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
// 根据属性获取对应的 ReactiveEffect
deps.push(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
// 省略
// 当前为 set 类型
case TriggerOpTypes.SET:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
const eventInfo = __DEV__
? { target, type, key, newValue, oldValue, oldTarget }
: undefined
if (deps.length === 1) {
if (deps[0]) {
if (__DEV__) {
triggerEffects(deps[0], eventInfo)
} else {
triggerEffects(deps[0])
}
}
} else {
const effects: ReactiveEffect[] = []
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
if (__DEV__) {
triggerEffects(createDep(effects), eventInfo)
} else {
triggerEffects(createDep(effects))
}
}
}
根据指定对象获取到对应的 Map 对象,此时 depsMap 为:

之后再根据指定属性获取对应的 ReactiveEffect,再添加到 deps 中,此时 deps 为:

后面我们只需关注 triggerEffects(deps[0], eventInfo) 这行代码, triggerEffects 函数也在packages/reactivity/src/effect.ts 文件中:
ts
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
// 获取到 ReactiveEffect 数组
const effects = isArray(dep) ? dep : [...dep]
for (const effect of effects) {
if (effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
for (const effect of effects) {
if (!effect.computed) {
// 实际执行是每个 effect 的 run 方法
triggerEffect(effect, debuggerEventExtraInfo)
}
}
}
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
} else {
// 实际执行的是传入的匿名函数 fn 方法
// 再次触发 getter 方法 从而进行赋值
effect.run()
}
}
}
可以看出 triggerEffects 函数实际先获取到 effect 数组,之后遍历数组执行每个 effect.run(),实际执行的是 fn 方法,该方法是最初依赖收集时传入的匿名函数,之后再次触发 getter 方法,从而进行赋值,至此整个依赖触发完成。

总结
reactive函数实际执行了createReactiveObject方法。createReactiveObject方法主要创建了一个proxy实例对象,给代理对象添加getter和setter行为,get、set方法主要在mutableHandlers对象中。get方法实际执行了createGetter方法,该方法中track函数来进行依赖收集,而set方法实际执行了createSetter方法,该方法中trigger进行依赖触发。effect函数实际创建了一个ReactiveEffect实例,该构造函数接收一个fn函数,等于传进来的匿名函数,该回调函数必须暴露getter行为。- 另外该构造函数还做了两件事,第一是在
run函数中给avtiveEffect赋值,第二是执行fn函数。 - 一旦
getter触发,就会激活track方法,构建WeakMap即targetMap对象,从而完成指定对象指定属性到effect的依赖收集的工作。 - 此时已经完成了一个依赖收集,之后进行依赖触发
setter。 set方法实际执行了createSetter方法,然后触发trigger函数进行依赖触发。trigger函数中首先或从之前targetMap依赖收集的对象中获取,根据key获取到effect,然后执行fn函数,从而完成一个依赖触发的过程。reactive缺陷:一是解构后不支持响应性,二是不支持基本类型,只能是对象。