Vue3源码reactivity响应式篇之reactive响应式对象的track与trigger
概览
在BaseReactiveHandler
类的get
方法中,有如下代码块if (!isReadonly2){track(target, "get", key);}
,这表示通过reactive
、shallowReactive
创建的响应式对象,非只读的,当读取代理对象proxyTarget
的某个属性key
时,都会被该get
方法拦截,即调用track()
方法建立依赖。
而当对代理对象proxyTarget
进行赋值或更新某个属性的值时,会被set
方法拦截,即调用trigger()
方法触发依赖(而删除会被deleteProperty
拦截)。
因此对于reactive
响应式对象的响应式处理,和track
与trigger
方法密不可分。
本文主要介绍track
与trigger
是如何进行依赖的收集与触发的全流程。
源码分析
在vue3中,维护了一个全局的targetMap
WeakMap
实例对象,用于存储响应式对象与依赖的映射关系。
const targetMap = new WeakMap();
track
方法
track
方法收集依赖就是往变量targetMap
中添加相关元素,存储响应式对象与依赖的映射关系。
track
的源码实现如下:
function track(target, type, key) {if (shouldTrack && activeSub) {let depsMap = targetMap.get(target);if (!depsMap) {targetMap.set(target, depsMap = new Map());}let dep = depsMap.get(key);if (!dep) {depsMap.set(key, dep = new Dep());dep.map = depsMap;dep.key = key;}dep.track();}
}
这里暂且不论shouldTrack
和activeSub
,假定满足track
的条件。首先从targetMap
中读取depsMap
,若depsMap
中不存在,则创建一个新的Map
的实例,并将其赋值给depsMap
,并且存储到targetMap
中。然后从depsMap
中获取key
对应的依赖关系dep
,同理,若dep
不存在,则创建一个新的Dep
实例,并将其赋值给dep
,并且存储到depsMap
中,然后将dep
的map
和key
分别绑定depsMap
、key
。最后调用dep.track()
方法。
dep.track()
执行后,会将当前的activeSub
添加到dep
的subs
数组中。
trigger
方法
track
方法的源码实现如下:
function trigger(target, type, key, newValue, oldValue, oldTarget) {const depsMap = targetMap.get(target);if (!depsMap) {globalVersion++;return;}const run = (dep) => {if (dep) {dep.trigger();}};startBatch();if (type === "clear") {depsMap.forEach(run);} else {const targetIsArray = isArray(target);const isArrayIndex = targetIsArray && isIntegerKey(key);if (targetIsArray && key === "length") {const newLength = Number(newValue);depsMap.forEach((dep, key2) => {if (key2 === "length" || key2 === ARRAY_ITERATE_KEY || !isSymbol(key2) && key2 >= newLength) {run(dep);}});} else {if (key !== void 0 || depsMap.has(void 0)) {run(depsMap.get(key));}if (isArrayIndex) {run(depsMap.get(ARRAY_ITERATE_KEY));}switch (type) {case "add":if (!targetIsArray) {run(depsMap.get(ITERATE_KEY));if (isMap(target)) {run(depsMap.get(MAP_KEY_ITERATE_KEY));}} else if (isArrayIndex) {run(depsMap.get("length"));}break;case "delete":if (!targetIsArray) {run(depsMap.get(ITERATE_KEY));if (isMap(target)) {run(depsMap.get(MAP_KEY_ITERATE_KEY));}}break;case "set":if (isMap(target)) {run(depsMap.get(ITERATE_KEY));}break;}}}endBatch();
}
trigger
方法在响应式数据发生变化后会被触发,vue3会先判断全局响应式集合对象targetMap
中是否存在依赖对象depsMap
,即是否有被追踪依赖,若不存在,则将globalVersion
加1,然后直接返回,没有下一步;定义run
方法;调用startBatch
方法,表示要进行批处理。判断type
是否clear
,是,则遍历依赖对象depsMap
,执行run
方法;否则判断target
是否是数组,以及key
是否数组的索引,然后根据type
和key
取出响应式数据对应的具体依赖,调用run
方法;最后调用endBatch
方法。