Tracking: Inject
根本限制:shared reference
inject() 不複製值,回傳的是與 provide() 完全相同的 RefImpl 引用。
A.num (RefImpl)
├─ B inject → 同一個 RefImpl
└─ C inject → 同一個 RefImpl
event.target 完全相同,無法用 valNodeMap 區分「這次 onTrack 在哪個 component」。
若覆寫 valNodeMap,最後一個執行 inject override 的 component 會污染所有後續查找,
導致第三、四層 component 的 props 連結到錯誤的節點。
解法:key 用共用引用,value 存自己的節點
Phase 1:
injectRawToNodeMap(module-level WeakMap)- key:父層 RefImpl(共用引用,不修改)
- value:子層自己建立的 inject node(per-component)
- 深度優先遍歷保證父層先寫,子層 prop 連結時查得到
- 刻意不寫入
valNodeMap,避免兄弟 component 互蓋
Anonymous node(provide 值不在 setupState)
- 當
valNodeMap找不到 provided value 時(如provide('key', ref(42))inline,ref 未被賦值到任何變數),在父層 graph entry 建一個節點 id: ${parentName}.anonymous:${key},varName: 'anonymous',type: 'ref' | 'reactive'- 建立後寫入
valNodeMap,兄弟 component inject 同一個 key 時直接命中,不重建 - 後續子層 inject 偵測正常連線,
deps指向此 anonymous node
- 當
Phase 2:
injectRawToLocalNode(per-component local Map)- 每次
triggerInstance重建,只包含當前 component 的 inject nodes resolveDepNode優先查這個 Map,命中即返回正確節點- 不跨 component 共享,無污染問題
- 每次
resolveDepNode 查找順序
injectRawToLocalNode.get(target) // inject(per-component,優先)
?? valNodeMap.get(target) // ref / reactive / computed
?? valNodeMap.get(rawSetupState[depName]) // Pinia store fallback
?? propKeyNodeMap.get(target)?.get(key) // props(target 是 rawPropsObj)
已知瓶頸
provide primitive(string / number / boolean):
typeof val !== "object"守衛同時過濾 provide 側與 child 側,inject node 不建立。使用者可改用ref()包裝來繞過。inject default value,provide 不存在:
parentProvides沒有此 key,provideRawToNode建不到,inject node 不建立。此情境設計上就沒有 provide 來源,圖中無來源節點屬預期行為。inject 封裝在 composable,原始值不暴露到 setupState:
rawSetupState找不到 inject 值,inject node 不建立。此限制連 Vue DevTools 也無法處理,因為 inject 值從未出現在任何 component 的公開 state 中。root component inject app.provide:
instance.parent === null,整段 inject 偵測邏輯被跳過。root component 直接 inject 屬極少見情境。inject 後立即解構 reactive:
const { foo } = inject('config')解構出 primitive,不在parentProvides範圍,比對失敗。reactive 解構後失去響應性,本身是 Vue 不推薦的寫法。
Source: allenstu6311/Vue-reactivity-graph — distributed by TomeVault.