00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Provide e Inject

Il problema del prop drilling

Quando devi passare dati da un componente genitore a un componente molto annidato, dovresti passare le props attraverso ogni livello intermedio. Questo pattern, chiamato prop drilling, rende il codice verboso e difficile da mantenere.

ComponenteA (ha i dati)
  └── ComponenteB (passa i dati, non li usa)
       └── ComponenteC (passa i dati, non li usa)
            └── ComponenteD (usa i dati)

provide e inject risolvono questo problema permettendo di passare dati direttamente da un antenato a qualsiasi discendente.

provide()

provide() rende un valore disponibile a tutti i componenti discendenti.

<!-- ComponenteAntenato.vue -->
<script setup lang="ts">
import { provide, ref, readonly } from 'vue'

const tema = ref<'chiaro' | 'scuro'>('chiaro')
const nomeApp = 'La Mia App'

// Provide di un valore semplice
provide('nomeApp', nomeApp)

// Provide di un valore reattivo
provide('tema', tema)

// Provide di funzioni
function cambiaTema() {
  tema.value = tema.value === 'chiaro' ? 'scuro' : 'chiaro'
}
provide('cambiaTema', cambiaTema)

// Provide di un valore readonly (consigliato per sicurezza)
provide('temaReadonly', readonly(tema))
</script>

<template>
  <div :class="tema">
    <h1>{{ nomeApp }}</h1>
    <slot />
  </div>
</template>

inject()

inject() recupera un valore fornito da un antenato.

<!-- ComponenteDiscendente.vue (a qualsiasi profondita) -->
<script setup lang="ts">
import { inject, type Ref } from 'vue'

// Inject base
const nomeApp = inject<string>('nomeApp')

// Inject con valore predefinito
const tema = inject<Ref<string>>('tema', ref('chiaro'))

// Inject di una funzione
const cambiaTema = inject<() => void>('cambiaTema', () => {})
</script>

<template>
  <div>
    <p>App: {{ nomeApp }}</p>
    <p>Tema: {{ tema }}</p>
    <button @click="cambiaTema">Cambia tema</button>
  </div>
</template>

Reattivita con provide/inject

Per mantenere la reattivita, fornisci ref o reactive. Le modifiche nel provider si propagano automaticamente a tutti gli inject.

<!-- ProviderUtente.vue -->
<script setup lang="ts">
import { provide, ref, computed } from 'vue'

interface Utente {
  id: number
  nome: string
  email: string
  ruolo: 'admin' | 'utente'
}

const utenteCorrente = ref<Utente | null>(null)
const isAutenticato = computed(() => utenteCorrente.value !== null)
const isAdmin = computed(() => utenteCorrente.value?.ruolo === 'admin')

async function login(email: string, password: string) {
  const risposta = await fetch('/api/login', {
    method: 'POST',
    body: JSON.stringify({ email, password })
  })
  utenteCorrente.value = await risposta.json()
}

function logout() {
  utenteCorrente.value = null
}

// Provide di un oggetto strutturato con dati e azioni
provide('auth', {
  utente: utenteCorrente,
  isAutenticato,
  isAdmin,
  login,
  logout
})
</script>

<template>
  <slot />
</template>
<!-- Qualsiasi discendente puo usare i dati di autenticazione -->
<script setup lang="ts">
import { inject, type Ref, type ComputedRef } from 'vue'

interface AuthContext {
  utente: Ref<any>
  isAutenticato: ComputedRef<boolean>
  isAdmin: ComputedRef<boolean>
  login: (email: string, password: string) => Promise<void>
  logout: () => void
}

const auth = inject<AuthContext>('auth')!

// Ora puoi usare auth.utente, auth.isAutenticato, ecc.
</script>

<template>
  <div v-if="auth.isAutenticato.value">
    <p>Benvenuto, {{ auth.utente.value.nome }}</p>
    <button @click="auth.logout">Logout</button>
  </div>
  <div v-else>
    <p>Effettua il login</p>
  </div>
</template>

Symbol keys

Per evitare collisioni di nomi, usa Symbol come chiave di provide/inject. Questo e il pattern consigliato per librerie e progetti grandi.

// chiavi-injection.ts
import type { InjectionKey, Ref, ComputedRef } from 'vue'

// Definisci interfacce
export interface TemaContext {
  tema: Ref<'chiaro' | 'scuro'>
  cambia: () => void
  colori: ComputedRef<{ sfondo: string; testo: string }>
}

export interface NotificaContext {
  mostra: (messaggio: string, tipo?: 'successo' | 'errore' | 'info') => void
  nascondi: (id: number) => void
}

// Crea chiavi tipizzate con Symbol
export const TEMA_KEY: InjectionKey<TemaContext> = Symbol('tema')
export const NOTIFICA_KEY: InjectionKey<NotificaContext> = Symbol('notifica')
<!-- Provider con Symbol key -->
<script setup lang="ts">
import { provide, ref, computed } from 'vue'
import { TEMA_KEY, type TemaContext } from '@/chiavi-injection'

const tema = ref<'chiaro' | 'scuro'>('chiaro')

const colori = computed(() => {
  if (tema.value === 'scuro') {
    return { sfondo: '#1a1a2e', testo: '#e0e0e0' }
  }
  return { sfondo: '#ffffff', testo: '#333333' }
})

function cambia() {
  tema.value = tema.value === 'chiaro' ? 'scuro' : 'chiaro'
}

// TypeScript sa esattamente cosa deve avere l'oggetto
provide(TEMA_KEY, { tema, cambia, colori })
</script>
<!-- Consumer con Symbol key -->
<script setup lang="ts">
import { inject } from 'vue'
import { TEMA_KEY } from '@/chiavi-injection'

// TypeScript conosce automaticamente il tipo
const temaContext = inject(TEMA_KEY)

if (!temaContext) {
  throw new Error('TEMA_KEY non fornito. Assicurati di usare TemaProvider.')
}

const { tema, cambia, colori } = temaContext
</script>

<template>
  <div :style="{ background: colori.sfondo, color: colori.testo }">
    <p>Tema: {{ tema }}</p>
    <button @click="cambia">Cambia tema</button>
  </div>
</template>

App-level provide

Puoi fornire valori a livello di applicazione in main.ts, rendendoli disponibili a tutti i componenti.

// main.ts
import { createApp, ref } from 'vue'
import App from './App.vue'
import { TEMA_KEY, NOTIFICA_KEY } from './chiavi-injection'

const app = createApp(App)

// Provide a livello app
app.provide(TEMA_KEY, {
  tema: ref('chiaro'),
  cambia: () => { /* ... */ },
  colori: computed(() => ({ sfondo: '#fff', testo: '#333' }))
})

// Provide della configurazione globale
app.provide('config', {
  apiUrl: import.meta.env.VITE_API_URL,
  appNome: 'La Mia App',
  versione: '1.0.0'
})

app.mount('#app')

Pattern composable con provide/inject

Il pattern piu pulito e creare composables che incapsulano provide e inject.

// composables/useTema.ts
import { provide, inject, ref, computed, type Ref, type ComputedRef } from 'vue'

interface TemaState {
  tema: Ref<'chiaro' | 'scuro'>
  isDark: ComputedRef<boolean>
  cambia: () => void
  imposta: (t: 'chiaro' | 'scuro') => void
}

const TEMA_SYMBOL = Symbol('tema')

// Chiamato dal componente provider (una sola volta)
export function provideTema(temaIniziale: 'chiaro' | 'scuro' = 'chiaro') {
  const tema = ref(temaIniziale)
  const isDark = computed(() => tema.value === 'scuro')

  function cambia() {
    tema.value = tema.value === 'chiaro' ? 'scuro' : 'chiaro'
  }

  function imposta(t: 'chiaro' | 'scuro') {
    tema.value = t
  }

  const state: TemaState = { tema, isDark, cambia, imposta }
  provide(TEMA_SYMBOL, state)

  return state
}

// Chiamato da qualsiasi discendente
export function useTema(): TemaState {
  const state = inject<TemaState>(TEMA_SYMBOL)

  if (!state) {
    throw new Error(
      'useTema() richiede provideTema() in un componente antenato'
    )
  }

  return state
}
<!-- App.vue - Provider -->
<script setup>
import { provideTema } from '@/composables/useTema'

const { tema, isDark } = provideTema('chiaro')
</script>

<template>
  <div :class="{ dark: isDark }">
    <router-view />
  </div>
</template>
<!-- Qualsiasi componente discendente -->
<script setup>
import { useTema } from '@/composables/useTema'

const { tema, cambia, isDark } = useTema()
</script>

<template>
  <button @click="cambia">
    {{ isDark ? 'Tema chiaro' : 'Tema scuro' }}
  </button>
</template>

Provide/inject vs Pinia

Caratteristica provide/inject Pinia
Ambito Albero componenti Globale app
DevTools Limitato Completo
SSR Da gestire Supporto nativo
Complessita Bassa Media
Uso ideale Contesto locale, configurazione Stato globale, dati condivisi
Persistenza No Con plugin

Best practice

  • Usa Symbol keys: Prevengono collisioni di nomi e abilitano la tipizzazione
  • Fornisci dati readonly quando possibile: Previeni modifiche accidentali con readonly()
  • Crea composables provide/inject: Incapsula provider e consumer in funzioni riutilizzabili
  • Lancia errori se inject fallisce: Fornisci messaggi chiari quando il provider manca
  • Non abusare di provide/inject: Usa Pinia per stato veramente globale
  • Documenta le dipendenze: Chi usa inject deve sapere quale provider e necessario
  • Mantieni il provider vicino ai consumer: Non mettere tutto a livello app se non necessario