# Vue Tracking Inject

> Vue Reactivity Graph 插件的 provide / inject 追蹤知識。說明 shared reference 造成的污染問題，以及解法：injectRawToNodeMap（module-level WeakMap）、injectRawToLocalNode（per-component Map）、anonymous node 建立機制，與 resolveDepNode 查找順序。涉及 inject 追蹤、provideRawToNode、resolveDepNode 時載入。 Use when this capability is needed.

- Skill: `tomevault-io/vue-tracking-inject` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/vue-tracking-inject`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/vue-tracking-inject/raw
- Safety review: pending (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/vue-tracking-inject

---


<!-- 來源：docs/tracking/inject.md -->

# 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](https://github.com/allenstu6311/Vue-reactivity-graph) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-15 -->

