今天我们有个艰巨的挑战,那就是阅读Vue的reactive源码,一篇写完太长了,我想要长痛分成多次短痛,这样感受起来就像无痛(手动doge),只有先读的懂了,我们才能下手模仿;加油吧宝子们!!PS:本文中的代码都是简单版,多余的已被剪枝,更加简单清晰易懂; 首先我们用的例子🌰还是上上上篇那个,这段代码是我们经常用到的reactive和effect的方法,今天我们就一起来深入挖掘Vue到底做了什么事;
js
<script>
const { reactive, effect } = Vue;
const obj = reactive({
name: "张三",
});
console.log(obj);
effect(() => {
document.querySelector("#app").innerText = obj.name;
});
setTimeout(() => {
obj.name = "李四";
}, 2000);
</script>
这是我们即将介绍的方法流程图,对照着看更容易理清思路;
首先来看reactive方法;
js
export function reactive(target: object) {
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
当前reactive方法里传入的是{name: "张三"}
对象;接下来看一下createReactiveObject方法;
js
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// 读取proxyMap的实例,此时的proxyMap是一个weakMap{}
const existingProxy = proxyMap.get(target)
// targetType为1
const targetType = getTargetType(target)
// 重点代码
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 此时target和proxy存入proxyMap,下次target存在直接用
proxyMap.set(target, proxy)
return proxy
}
createReactiveObject传入的target依然是{name: "张三"}
对象;
第13行Proxy第一个参数传入target作为被代理对象;第二个参数传入先判断targetType,此时targetType
不等于TargetType.COLLECTION
,因此会用baseHandlers;,new Proxy生成一个proxy,然后被return回来;reactive函数结束之后会进入effect方法;
我们来看一下effect方法都做了什么事;
js
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
// ReactiveEffect是一个class,有run和stop方法
const _effect = new ReactiveEffect(fn)
if (!options || !options.lazy) {
_effect.run()
}
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
effect第一个参数fn是我们传入的() => { document.querySelector("#app").innerText = obj.name; }
ReactiveEffect的run方法:
js
run() {
try {
// this是当前ReactiveEffect的实例,它的parent是activeEffect
// 此时activeEffect是undefined
this.parent = activeEffect
activeEffect = this
shouldTrack = true
// 执行我们传入的方法,document.querySelector("#app").innerText = obj.name;
return this.fn()
}
}
第6行的activeEffect被赋值,此时它的fn为下图
接着会进入到baseHandlers里有get方法叫createGetter;此时get方法里的target还是{name: "张三"}
对象,key是name
,receiver: Proxy(Object) {name: "张三"}
; Vue响应性的核心是依赖收集和依赖触发;track方法就是依赖收集的过程;
js
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// target是对象不是数组
const targetIsArray = isArray(target)
// res的值是张三
const res = Reflect.get(target, key, receiver)
if (!isReadonly) {
// track方法非常重要!
track(target, TrackOpTypes.GET, key)
}
return res
}
}
接着来看下核心的track方法:
js
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
// 此时的targetMap是WeakMap{}
let depsMap = targetMap.get(target)
if (!depsMap) {
// 为targetMap赋值
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep()))
}
trackEffects(dep, eventInfo)
}
}
第2行的activeEffect就是上文的;
第7行为targetMap赋值之后就变成了:
这里的targeMap比较复杂,是下图一个结构:
最后执行trackEffects方法:
js
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false
if (shouldTrack) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
}
}
第7行的dep是这样的,和targetMap对比一下,会发现dep和和targetMap的value中第一个元素一样的,他们是指向同一个内存空间;
第8行里targetMap就保存了一个ReactiveEffect;这时已经完成了依赖收集的过程;依赖收集的本质上就是建立起targetMap和ReactiveEffect的关联;此时我们就可以在targetMap中用key来获取到ReactiveEffect;ReactiveEffect中存在一个fn函数,它就是我们传入的() => { document.querySelector("#app").innerText = obj.name; }
方法;
track方法执行完了,再回到createGetter方法:
js
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// target是对象不是数组
const targetIsArray = isArray(target)
// res的值是张三
const res = Reflect.get(target, key, receiver)
if (!isReadonly) {
// track方法非常重要!
track(target, TrackOpTypes.GET, key)
}
return res
}
}
第13行return res,这时的res就是张三
,这里也就是obj.name得到了张三
;
到这里就完成了effect的处理逻辑;
让我们总结一下effect的过程:
- 生成ReactiveEffect实例;
- 触发fn方法,从而激活getter;
- 建立了targetMap和activeEffect之间的联系;