Modul 3: Komponenten

Provide / Inject

Daten durch den Komponentenbaum tunneln — ohne Props-Drilling.

Das Problem: Props-Drilling

Wenn eine tief verschachtelte Komponente Daten braucht, müsstest du diese Props durch jede Zwischenebene durchreichen — auch wenn die Zwischenkomponenten die Daten selbst gar nicht nutzen. Das nennt man Props-Drilling.

Props-Drilling — das Problem
<!-- ❌ Props-Drilling: Jede Ebene muss "theme" weiterreichen -->
<GrandParent :theme="theme">         <!-- hat theme -->
  <Parent :theme="theme">            <!-- braucht es nicht, reicht nur weiter -->
    <Child :theme="theme">           <!-- braucht es nicht, reicht nur weiter -->
      <DeepChild :theme="theme" />   <!-- braucht es endlich! -->
    </Child>
  </Parent>
</GrandParent>
🛤️

Rails-Vergleich: Helper-Methoden & Concerns

In Rails nutzt du helper_method :current_user in Controllern, damit Views und Partials überall auf current_user zugreifen können — ohne es durch jedes Partial als Local durchzureichen. provide/inject löst dasselbe Problem in Vue.

Daten bereitstellen mit provide()

provide() macht einen Wert für alle Nachkommen einer Komponente verfügbar — egal wie tief verschachtelt.

ThemeProvider.vue
<script setup lang="ts">
import { provide, ref } from 'vue'

const theme = ref('dark')

// Alle Nachkommen können 'theme' injecten
provide('theme', theme)

// Auch Funktionen bereitstellen
provide('toggleTheme', () => {
  theme.value = theme.value === 'dark' ? 'light' : 'dark'
})
</script>

<template>
  <div :class="theme">
    <slot />
  </div>
</template>

Daten empfangen mit inject()

Jede Nachkommen-Komponente kann mit inject() auf bereitgestellte Werte zugreifen. Der zweite Parameter ist ein Fallback-Wert.

ThemedButton.vue
<script setup lang="ts">
import { inject } from 'vue'

// Wert empfangen mit Fallback
const theme = inject('theme', ref('light'))
const toggleTheme = inject<() => void>('toggleTheme')
</script>

<template>
  <button
    :class="theme === 'dark' ? 'bg-gray-800' : 'bg-white'"
    @click="toggleTheme?.()"
  >
    Theme wechseln ({{ theme }})
  </button>
</template>
⚠️

Inject nur in Nachkommen

inject() findet nur Werte, die von einer Eltern-Komponente (oder App-Level) bereitgestellt wurden. Geschwister-Komponenten sehen die Werte nicht.

Typsicherheit mit InjectionKey

Vue bietet InjectionKey<T> als typsicheren Schlüssel. So vermeidest du String-basierte Keys und bekommst volle Autovervollständigung.

keys.ts
import type { InjectionKey, Ref } from 'vue'

// Typsicherer Key — generisch typisiert
export const themeKey: InjectionKey<Ref<string>> = Symbol('theme')

export interface ThemeContext {
  theme: Ref<string>
  toggleTheme: () => void
}

export const themeContextKey: InjectionKey<ThemeContext> = Symbol('themeContext')
Verwendung
<script setup lang="ts">
import { provide, inject } from 'vue'
import { themeContextKey } from '~/keys'

// Provider
const theme = ref('dark')
provide(themeContextKey, {
  theme,
  toggleTheme: () => {
    theme.value = theme.value === 'dark' ? 'light' : 'dark'
  },
})

// Consumer (in anderer Komponente)
const ctx = inject(themeContextKey)
// ctx ist automatisch typisiert als ThemeContext | undefined
ctx?.toggleTheme()
</script>

Reaktive Werte bereitstellen

Damit Nachkommen auf Änderungen reagieren, solltest du ref() oder computed() über provide weitergeben — nicht nur statische Werte.

Reaktives Provide
<script setup lang="ts">
import { provide, ref, readonly, computed } from 'vue'

const count = ref(0)

// ✅ Reaktiver Wert — Nachkommen sehen Änderungen
provide('count', readonly(count))

// ✅ Computed — wird automatisch aktualisiert
provide('doubleCount', computed(() => count.value * 2))

// ✅ Setter-Funktion bereitstellen
provide('increment', () => count.value++)

// ❌ NICHT: provide('count', count.value)
// → Verliert Reaktivität! Nur der initiale Wert wird übergeben.
</script>
💡

readonly() für Sicherheit

Wenn Nachkommen den Wert nicht ändern sollen, wickle ihn in readonly() ein. So bleibt der Einweg-Datenfluss gewahrt.

App-Level Provide

In Nuxt kannst du Werte app-weit über ein Plugin bereitstellen. So sind sie in jeder Komponente verfügbar.

plugins/theme.ts
// plugins/theme.ts
export default defineNuxtPlugin((nuxtApp) => {
  const theme = useState('theme', () => 'light')

  // App-weit bereitstellen
  nuxtApp.vueApp.provide('appTheme', theme)

  nuxtApp.vueApp.provide('setTheme', (newTheme: string) => {
    theme.value = newTheme
  })
})

Interaktive Demo

Simulierter Theme-Provider: Die Eltern-Komponente stellt ein Farbthema bereit, das alle verschachtelten Komponenten automatisch verwenden.

Theme-Provider Demo
Interaktiv
🎨 provide('theme', ocean)
📦 LayoutWrapper
📦 ContentArea
🧩 ThemedButton — inject('theme') → ocean
🧩 ThemedBadge — inject('theme')
Status: Aktiv
ℹ️

Beachte: Keine einzige Zwischenkomponente (LayoutWrapper, ContentArea) musste das Theme als Prop empfangen. provide/inject tunnelt direkt durch.

Zusammenfassung

ℹ️

Kernpunkte

  • provide() stellt Daten für alle Nachkommen bereit
  • inject() empfängt die Daten — egal wie tief verschachtelt
  • InjectionKey<T> sorgt für Typsicherheit
  • Reaktive Werte (ref/computed) providieren, nicht statische
  • readonly() schützt vor ungewollten Änderungen durch Nachkommen
  • App-Level Provide über Nuxt-Plugins für globale Werte nutzen