本文为原创文章,未获授权禁止转载,侵权必究!
本篇是 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
缺陷:一是解构后不支持响应性,二是不支持基本类型,只能是对象。