概覽
在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
方法。