哈嘍,各位小夥伴,歡迎來到我是wangfang呀的博客!我是我是wangfang呀,雖然還在編程的“菜鳥”階段,但我已經迫不及待地想和大家分享我一路上踩過的坑和學到的小技巧。如果你也曾為bug頭疼,那麼你來對地方了!今天的內容希望能夠給大家帶來一些靈感和幫助。

前言

場景痛點

  • 祖孫隔着十幾層,props drilling 傳得人崩潰?
  • 抽象組件 / 組件庫想把「配置 & 能力」塞給後代,卻不想再加一堆 props? 解決方案 —— Vue 自帶 依賴注入provide → inject 祖先 一次 提供,後代 隨取隨用,中間層組件 零感知

1. 基本 API:一句話通電

<!-- Provider.vue -->
<script setup>
import { provide } from 'vue'

const theme = ref<'light' | 'dark'>('light')
provide('theme', theme)     // ① 提供
</script>
<!-- DeepChild.vue -->
<script setup>
import { inject } from 'vue'
const theme = inject<'light' | 'dark'>('theme', 'light')   // ② 注入 + 默認值
</script>

<template>
  <p>當前主題:{{ theme }}</p>
</template>
  • Key 可以是字符串,也推薦用 Symbol() 防衝突: const ThemeKey = Symbol()
  • 如果祖先找不到,返回 默認值(第二參數);不寫默認值則為 undefined

2. 為什麼用它?典型跨層級通信場景

場景 provide inject 誰用
Design System 主題 顏色/暗黑模式對象 reactive() 各個按鈕/卡片自行讀取
Form 表單上下文 registerField / validateAll 函數、表單配置 每個 <FormItem>
多 Tab 可插拔佈局 addPane / activeName 自定義 Pane 組件
國際化 I18n t 翻譯函數 任意深度組件

3. 響應式“陷阱”與對策

3.1 只注入值不響應?—— 確保提供的是“響應式對象”

// ❌ 不會聯動
provide('color', 'red')

// ✅ 提供 ref/reactive
provide('color', ref('red'))

規則inject 拿到的是 “同一個引用”。 只要 provide 時給的是 響應式,後代就能聯動。

3.2 深層組件更改數據 → 祖先不想被影響?

  • 方案 ①:只讀克隆

    const config = readonly(inject(ConfigKey))
    
  • 方案 ②:provide 函數 而非原始引用

    provide('setTheme', (v) => theme.value = v)
    

3.3 類型提示缺失?

  • Symbol + TS 泛型

    export const ThemeKey = Symbol() as InjectionKey<Ref<'light'|'dark'>>
    provide(ThemeKey, theme)
    const theme = inject(ThemeKey)!
    

4. 實戰案例:極簡表單庫骨架

4.1 Form.vue —— Provider

<script setup lang="ts">
import { provide, reactive } from 'vue'
import { FormKey } from './keys'

const fields = reactive<{ validate:()=>boolean }[]>([])

function register(field)   { fields.push(field) }
function validateAll()     { return fields.every(f => f.validate()) }

provide(FormKey, { register, validateAll })
</script>

<template><form><slot/></form></template>

4.2 FormItem.vue —— 子組件注入

<script setup lang="ts">
import { inject, onMounted } from 'vue'
import { FormKey } from './keys'

const form = inject(FormKey)!
function validate() { /* 校驗邏輯 */ }

onMounted(() => form.register({ validate }))
</script>

🚀 結果:父表單不必顯式傳方法,FormItem 自動上報;Form 調 validateAll() 即可一次性校驗——整潔、鬆耦合、可插拔


5. Quick Reference

操作 代碼 備註
基本提供 provide('key', value)
默認值 inject('key', default)
Symbol Key + 類型 const key = Symbol() as InjectionKey<T>
多層轉發 中間層再次 provide('key', inject('key'))
響應式只讀 readonly(inject(key))
插件式 provide app.provide(key, value) (根級全局)

✨ 小結

  1. props drilling → provide/inject:層級越深越香。
  2. 提供 響應式對象或函數,確保聯動 & 可控。
  3. 適用“一處提供,後代隨取”的場景,非跨樹通信
  4. EventEmitter / Pinia 並不衝突,各管各的層級與粒度。

掌握依賴注入 = 組件庫 & 複雜交互的“高級解耦術” —— 用好了,父子層級再深也從容不亂!

好啦,今天的內容就先到這裏!如果覺得我的分享對你有幫助,給我點個贊,順便評論吐個槽,當然不要忘了三連哦!感謝大家的支持,記得常回來,我是wangfang呀等着你們的下一次訪問!