Vue3 元件通信 – provide / inject
簡介
在大型 Vue 應用程式中,元件之間的資料傳遞往往不只局限於父子關係。provide / inject 讓我們可以在「祖先」元件一次性提供資料,讓任意深度的子孫元件直接取得,省去層層 props 轉遞或繁瑣的事件機制。
這套機制特別適合 全局設定、主題樣式、國際化、共享狀態 等情境。掌握它不僅能讓程式碼更乾淨,也能提升可維護性與重用性,是 Vue3 進階開發者必備的工具。
核心概念
1. 基本使用方式
provide 在祖先元件內宣告,inject 在子孫元件內取得。兩者以鍵值 (key) 為橋樑,鍵可以是字串或 Symbol(後者更安全)。
// Parent.vue
<script setup>
import { provide } from 'vue'
const theme = 'dark' // 可以是任何資料型別
provide('appTheme', theme) // 使用字串作為 key
</script>
<template>
<slot></slot> <!-- 讓子元件插入 -->
</template>
// Child.vue
<script setup>
import { inject } from 'vue'
const theme = inject('appTheme') // 直接取得祖先提供的值
</script>
<template>
<div>目前主題:{{ theme }}</div>
</template>
注意:
inject只能在setup()或script setup中使用,不能在普通的 Options API 中直接呼叫。
2. 提供可響應的資料
若提供的值是 ref、reactive,子孫元件取得後仍會保持響應式。
// Provider.vue
<script setup>
import { ref, provide } from 'vue'
const count = ref(0)
provide('counter', count) // 提供 ref
</script>
<template>
<button @click="count++">+1</button>
<slot></slot>
</template>
// Descendant.vue
<script setup>
import { inject } from 'vue'
const count = inject('counter') // 取得同一個 ref
</script>
<template>
<p>計數值:{{ count }}</p> <!-- 會自動更新 -->
</template>
3. 使用 Symbol 作為鍵
字串鍵雖然直觀,但在大型專案中容易與其他元件衝突。使用 Symbol 可保證唯一性。
// token.js
export const ThemeSymbol = Symbol('appTheme')
// RootProvider.vue
<script setup>
import { provide } from 'vue'
import { ThemeSymbol } from './token.js'
provide(ThemeSymbol, { mode: 'light' })
</script>
// AnyChild.vue
<script setup>
import { inject } from 'vue'
import { ThemeSymbol } from './token.js'
const theme = inject(ThemeSymbol)
</script>
4. 提供函式(Factory)
有時候需要根據子元件的需求動態產生資料,這時可以提供 factory function。
// ServiceProvider.vue
<script setup>
import { provide } from 'vue'
function createLogger(name) {
return (msg) => console.log(`[${name}] ${msg}`)
}
provide('loggerFactory', createLogger)
</script>
// LoggerUser.vue
<script setup>
import { inject } from 'vue'
const loggerFactory = inject('loggerFactory')
const logger = loggerFactory('MyComponent')
logger('Hello world!')
</script>
5. 結合 Composition API 建立可重用的「provide」
將 provide 包裝成自訂 composable,讓多個元件只要呼叫一次即可完成設定。
// useThemeProvider.js
import { ref, provide } from 'vue'
export const ThemeKey = Symbol('theme')
export function useThemeProvider(initial = 'dark') {
const mode = ref(initial)
const toggle = () => (mode.value = mode.value === 'dark' ? 'light' : 'dark')
provide(ThemeKey, { mode, toggle })
}
// App.vue
<script setup>
import { useThemeProvider } from './useThemeProvider.js'
useThemeProvider()
</script>
<template>
<router-view />
</template>
// ThemeToggler.vue
<script setup>
import { inject } from 'vue'
import { ThemeKey } from './useThemeProvider.js'
const { mode, toggle } = inject(ThemeKey)
</script>
<template>
<button @click="toggle">切換主題 ({{ mode }})</button>
</template>
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解法 |
|---|---|---|
| 忘記提供預設值 | inject 若找不到對應的 key,會回傳 undefined,導致渲染錯誤。 |
在 inject 時傳入第二個參數作為預設值:inject('key', defaultValue) |
| 使用非響應式資料 | 提供普通物件後子元件不會自動更新。 | 若需要雙向更新,使用 ref / reactive 包裝;或提供 setter 函式。 |
| 鍵名衝突 | 多個模組使用相同字串鍵會互相覆寫。 | 使用 Symbol 或集中管理的 token 檔案。 |
| 過度依賴 | 把大量全局狀態都放在 provide / inject,會失去 Vuex / Pinia 的可追蹤優勢。 |
僅在「跨層級共享且不需要頻繁變更」的情境使用,較大的全局狀態仍建議使用 Pinia。 |
| 無法在 Options API 中直接使用 | inject 必須在 setup 裡呼叫。 |
若仍想使用 Options API,可在 created 內透過 this.$.setupState 取得,或改寫成 mixins。 |
最佳實踐
- 鍵名統一管理:建立
constants/tokens.js,全部使用Symbol。 - 只提供:
provide的原則是 只提供,不在子元件中改變(除非提供的是ref)。 - 文件化:在專案文件中註明每個
provide的目的、型別與預設值,方便團隊協作。 - 組合式封裝:將相關的
provide / inject包裝成 composable,提升可測試性與重用性。
實際應用場景
主題 (Theme) 切換
- 祖先元件提供當前主題與切換函式,所有子元件只要
inject即可直接讀取與切換,避免每層都傳prop。
- 祖先元件提供當前主題與切換函式,所有子元件只要
表單驗證規則
- 在表單容器
FormProvider中provide整體的驗證規則或錯誤訊息,子欄位FormItem只需要inject取得即可。
- 在表單容器
多語系 (i18n) 文字
- 透過
provide注入當前語系的翻譯函式t(key),讓深層的 UI 元件不必一次次傳遞locale。
- 透過
第三方插件的全局實例
- 如
axios、socket.io、Mapbox等,在根元件provide一個實例,子元件直接inject使用,保持單例。
- 如
動態表格欄位設定
- 父層根據使用者權限
provide欄位可見性與編輯權限,子層表格元件自動根據注入的設定渲染。
- 父層根據使用者權限
總結
provide / inject 是 Vue3 為了解決「跨層級」資料共享而設計的輕量級機制。
- 透過 鍵值 連結祖先與子孫,省去層層傳遞。
- 使用
ref/reactive可保持 響應式,配合Symbol確保唯一性。 - 函式工廠、Composable 包裝 讓它更彈性且易於重用。
在實務開發中,將 provide / inject 用於 主題、i18n、全局服務、表單規則 等「不頻繁變更」的共享資訊,能讓程式碼結構更清晰、維護成本更低。記得遵守 鍵名統一管理、只提供不改變 的原則,並在需要更完整狀態管理時搭配 Pinia。掌握這套工具,你的 Vue3 專案將更具可擴充性與可讀性。