Zum Inhalt springen

Provide / Inject

Provide/Inject ermöglicht es, Daten von einer Eltern-Komponente an beliebig tief verschachtelte Kind-Komponenten weiterzugeben - ohne Props durch jede Ebene zu reichen.

┌─────────────────────────────────────────┐
│ App (hat theme) │
│ └─ Layout (braucht theme nicht) │
│ └─ Sidebar (braucht theme nicht) │
│ └─ Menu (braucht theme nicht)│
│ └─ MenuItem (braucht theme!) │
└─────────────────────────────────────────┘
<!-- Jede Ebene muss prop weiterreichen -->
<Layout :theme="theme">
<Sidebar :theme="theme">
<Menu :theme="theme">
<MenuItem :theme="theme" />
ParentComponent.vue
<script setup>
import { provide, ref, readonly } from 'vue'
const theme = ref('dark')
const user = ref({ name: 'Max', role: 'admin' })
// Einfacher Wert
provide('theme', theme)
// Readonly um Mutation von Kindern zu verhindern
provide('user', readonly(user))
// Objekt mit Werten und Methoden
provide('auth', {
user,
login: (credentials) => { /* ... */ },
logout: () => { user.value = null }
})
</script>
DeepChildComponent.vue
<script setup>
import { inject } from 'vue'
// Einfaches Inject
const theme = inject('theme')
// Mit Default-Wert
const locale = inject('locale', 'de')
// Mit Factory-Funktion als Default
const config = inject('config', () => ({ debug: false }), true)
// Readonly User
const user = inject('user')
console.log(user.value.name) // 'Max'
// user.value.name = 'Anna' // Warnung wenn readonly
</script>
<template>
<div :class="theme">
Willkommen, {{ user?.name }}
</div>
</template>
<script>
export default {
data() {
return { theme: 'dark' }
},
// Als Funktion für reaktive Daten
provide() {
return {
theme: this.theme,
// So nicht reaktiv!
}
}
}
</script>
<!-- Provider -->
<script setup>
import { provide, ref, computed } from 'vue'
const count = ref(0)
// Ref direkt providieren - bleibt reaktiv
provide('count', count)
// Computed providieren
provide('doubleCount', computed(() => count.value * 2))
// Reaktives Objekt
provide('state', reactive({ count: 0, name: 'Max' }))
</script>
<!-- Consumer -->
<script setup>
import { inject } from 'vue'
const count = inject('count')
// count ist ein Ref - reaktiv!
const doubleCount = inject('doubleCount')
// Aktualisiert sich automatisch
</script>

Änderungen sollten nur dort passieren, wo die Daten providet werden:

<!-- Provider -->
<script setup>
import { provide, ref, readonly } from 'vue'
const count = ref(0)
// Methode zum Ändern
function increment() {
count.value++
}
// Readonly Wert + Methode providieren
provide('counter', {
count: readonly(count),
increment
})
</script>
<!-- Consumer -->
<script setup>
const { count, increment } = inject('counter')
// count.value++ // Nicht möglich (readonly)
increment() // Über bereitgestellte Methode
</script>
keys.ts
import type { InjectionKey, Ref } from 'vue'
export interface User {
id: number
name: string
role: 'admin' | 'user'
}
// Typisierter Injection Key
export const userKey: InjectionKey<Ref<User | null>> = Symbol('user')
export const themeKey: InjectionKey<Ref<'light' | 'dark'>> = Symbol('theme')
<script setup lang="ts">
import { provide, inject, ref } from 'vue'
import { userKey, themeKey } from './keys'
// Provider - TypeScript kennt den Typ
provide(userKey, ref({ id: 1, name: 'Max', role: 'admin' }))
// Consumer - Automatisch typisiert
const user = inject(userKey) // Ref<User | null> | undefined
const theme = inject(themeKey, ref('light')) // Ref<'light' | 'dark'>
</script>
main.js
import { createApp, ref } from 'vue'
import App from './App.vue'
const app = createApp(App)
// Global für alle Komponenten verfügbar
app.provide('appName', 'Meine App')
app.provide('apiUrl', 'https://api.example.com')
// Auch reaktive Werte
const globalTheme = ref('light')
app.provide('theme', globalTheme)
app.mount('#app')
<!-- Beliebige Komponente -->
<script setup>
const appName = inject('appName') // 'Meine App'
const theme = inject('theme') // Ref<'light'>
</script>
ThemeProvider.vue
<script setup>
import { provide, ref, computed } from 'vue'
const isDark = ref(false)
const theme = computed(() => ({
mode: isDark.value ? 'dark' : 'light',
colors: isDark.value
? { bg: '#1a1a1a', text: '#ffffff' }
: { bg: '#ffffff', text: '#1a1a1a' }
}))
function toggleTheme() {
isDark.value = !isDark.value
}
provide('theme', { theme, toggleTheme })
</script>
<template>
<div :class="theme.mode">
<slot />
</div>
</template>
  • Direkte Eltern-Kind-Beziehung
  • Explizite Datenflüsse gewünscht
  • Komponente wiederverwendbar sein soll
  • 1-2 Ebenen Tiefe
  • Symbol Keys für Type Safety
  • readonly() für schreibgeschützte Daten
  • Methoden für Mutationen providieren
  • Defaults bei inject() angeben
  • Für echte DI-Szenarien nutzen
  • Als Ersatz für Pinia/State Management
  • String Keys in großen Apps
  • Reaktive Daten ohne readonly
  • Tiefe Objektmutationen erlauben
  • Zu viele unabhängige provides

Offizielle Vue Dokumentation:

API Referenz: