Vue 中 provide 與 inject 的使用方法

在 Vue 組件樹中,當需要跨多層級傳遞數據時,一層層用 props 傳遞會變得繁瑣,就像接力賽要經過多個人傳遞一樣低效。這時候 provide 與 inject 就像一對 “數據快遞通道”,能讓父組件直接把數據 “發送” 給任意層級的子組件,跳過中間層,讓深層級通信更簡潔。

最基礎的用法是父組件通過 provide 提供數據,任意子組件(無論層級多深)用 inject 接收。比如在根組件提供用户登錄狀態,讓深層的按鈕組件能直接使用:

<!-- 父組件(如App.vue) -->
<template>
  <div class="app">
    <h3>根組件</h3>
    <button @click="isLogin = !isLogin">
      {{ isLogin ? '退出登錄' : '登錄' }}
    </button>
    <ChildComponent />
  </div>
</template>

<script setup>
import { ref, provide } from 'vue'
import ChildComponent from './ChildComponent.vue'

// 登錄狀態數據
const isLogin = ref(false)

// 提供數據:第一個參數是注入名,第二個是要提供的數據
provide('userLoginStatus', isLogin)
</script>

<!-- 子組件(中間層,無需處理數據) -->
<template>
  <div class="child">
    <h4>子組件</h4>
    <GrandChildComponent />
  </div>
</template>

<script setup>
import GrandChildComponent from './GrandChildComponent.vue'
</script>

<!-- 孫組件(深層級組件) -->
<template>
  <div class="grand-child">
    <h5>孫組件</h5>
    <button v-if="isLogin" class="operate-btn">
      編輯內容(僅登錄可見)
    </button>
    <p v-else>請登錄後操作</p>
  </div>
</template>

<script setup>
import { inject } from 'vue'

// 接收數據:參數是父組件提供的注入名
const isLogin = inject('userLoginStatus')
</script>

這裏根組件通過provide('userLoginStatus', isLogin)提供登錄狀態,中間的子組件無需任何處理,孫組件直接用inject('userLoginStatus')就能拿到數據,實現了跨層級的數據傳遞,避免了 props 的層層傳遞。

provide 與 inject 還支持傳遞響應式數據,當提供的數據變化時,所有注入該數據的組件都會自動更新。比如實現一個主題切換功能,讓所有組件同步響應主題變化:

<!-- 主題提供組件 -->
<template>
  <div :class="theme">
    <h3>主題切換</h3>
    <button @click="theme = theme === 'light' ? 'dark' : 'light'">
      切換到{{ theme === 'light' ? '深色' : '淺色' }}模式
    </button>
    <DeepComponent />
  </div>
</template>

<script setup>
import { ref, provide } from 'vue'
import DeepComponent from './DeepComponent.vue'

// 響應式主題數據
const theme = ref('light')

// 提供響應式數據
provide('appTheme', theme)
</script>

<style>
.light {
  background: white;
  color: #333;
}
.dark {
  background: #333;
  color: white;
}
</style>

<!-- 深層組件 -->
<template>
  <div class="deep">
    <p>當前主題:{{ theme }}</p>
  </div>
</template>

<script setup>
import { inject } from 'vue'

// 接收響應式主題數據
const theme = inject('appTheme')
</script>

當點擊切換按鈕改變theme的值時,提供組件和所有注入appTheme的組件都會實時更新樣式,因為注入的數據保持了響應式特性,這種方式特別適合全局狀態的共享。

為了讓注入更可靠,可以給 inject 設置默認值,當找不到對應的數據時,會使用默認值而不是返回 undefined。比如給用户角色設置默認值:

<!-- 子組件 -->
<template>
  <div>
    <p>用户角色:{{ userRole }}</p>
  </div>
</template>

<script setup>
import { inject } from 'vue'

// 設置默認值:當沒有提供'userRole'時使用'visitor'
const userRole = inject('userRole', 'visitor')
</script>

如果默認值需要是一個複雜對象(如對象、數組),最好用工廠函數返回,避免多個組件實例共享同一個默認值對象:

<script setup>
import { inject } from 'vue'

// 複雜默認值用工廠函數
const config = inject('appConfig', () => ({
  size: 'medium',
  layout: 'grid'
}))
</script>

provide 與 inject 還支持注入別名,當注入名可能衝突時,可以通過對象形式指定別名,讓代碼更清晰:

<!-- 提供組件 -->
<script setup>
import { provide } from 'vue'

const userInfo = { name: '張三', age: 25 }
provide('user', userInfo)
</script>

<!-- 注入組件 -->
<script setup>
import { inject } from 'vue'

// 給注入的數據起別名
const currentUser = inject('user')
</script>

需要注意的是,provide 與 inject 主要用於跨層級的深層通信,比如插件開發、全局配置等場景。對於父子組件或相鄰層級的通信,使用 props 和 emits 會更合適,因為它們的數據流更明確。另外,雖然子組件可以修改注入的數據,但最好遵循單向數據流原則,在子組件中通過 emits 通知父組件修改,保持數據變化可追蹤。

總的來説,provide 與 inject 就像組件樹中的 “數據隧道”,讓深層級通信變得簡單直接,尤其在大型應用中能減少 props 傳遞的冗餘代碼,讓組件結構更清晰。合理使用這對工具,能讓 Vue 的組件通信方式更加靈活多樣。