Forzare la ri-renderizzazione di un componente

In Vue esistono diverse tecniche per forzare la ri-renderizzazione dei componenti, vediamole insieme

Uno dei vantaggi che troviamo nei framework moderni come Vue è la gestione automatica del rendering degli elementi, è infatti il framework stesso che si occupa di andare a ridisegnare gli elementi all'interno della pagina quando necessario in modo tale da ottimizzare le performance.

Esistono però dei casi particolari, come ad esempio se stiamo lavorando in un'applicazione particolarmente complessa o magari non organizzata benissimo, dove abbiamo la necessità di forzare la ri-renderizzazione di un componente.

Per fare questa operazione esistono diverse strategie ognuna con i suoi pregi e difetti.

Attenzione: Se dobbiamo utilizzare una di queste tecniche probabilmente non stiamo utilizzando il framework nel modo corretto quindi prima di procedere valutiamo con attenzione se esistono soluzioni migliori.

# La soluzione brutale: ricaricare tutta la pagina

Questa è la soluzione più brutale tra tutte e dovrebbe essere evitata il più possibile. utilizzare questa tecnica l'equivalente di riavviare il computer ogni volta che vogliamo chiudere un'applicazione. Se abbiamo valutato attentamente il problema e abbiamo deciso in ogni caso di procedere con questa soluzione possiamo utilizzare La proprietà window.location per ricaricare la pagina o navigare verso una nuova rotta.

// Ricarica la pagina
window.location.reload()

// Naviga verso una nuova rotta
window.location.replace("")

# La soluzione brutta: $nextTick e v-if

A differenza della soluzione precedente con questa tecnica possiamo ricaricare il componente che ci interessa senza dover ricaricare l'intera pagina. Attraverso la funzione $nextTick e la direttiva v-if, che renderizza un componente solamente quando ha un valore true, possiamo utilizzare un piccolo trucchetto che va a modificare questo valore a false per una frazione di secondo in modo tale da forzare la ri-renderizzazione del componente.

<template>
  <b-button v-if="isRendered" />
</template>

<script>
  export default {
    data() {
      return {
        isRendered: true
      }
    }
    methods: {
      async forceRerender() {
        // Disabilita il rendering del componente
        this.isRendered = false

        // Attende il prossimo ciclio di aggiornamento della view
        await this.$nextTick()

        // Abilita il rendering del componente
        this.isRendered = true
      }
    }
  }
</script>

Questa tecnica va a distruggere a ricreare il componente quindi verrà rieseguito tutto il ciclo di vita, verranno quindi richiamanti di vari hook di created, mounted, ecc.

# Una soluzione migliore: $forceUpdate

A differenza della soluzione precedente questa tecnica ci permette di ricaricare il componente senza però andarlo a distruggere. Questa soluzione è particolarmente utile quando riscontriamo dei problemi con la reattività di vue, può capitare infatti che in casi particolari dvue non riesca a rilevare i cambiamenti di una prop o di un data (solitamente capita su oggetti e array) ed è in casi come questo che la funzione $forceUpdate ci può tornare utile.

<template>
  <ul>
    <li v-for="(item, index) of items" :key="index">
      #{{ item.id }} {{ item.label }}
    </li>
  </ul>
</template>

<script>
  export default {
    data() {
      return {
        items: [
          { id: "abc", label: "Prova 1" },
          { id: "123", label: "Prova 2" }
        ]
      }
    }
    methods: {
      async setLabel() {
        // Imposta la proprietà label del secondo item
        this.items[1].label = "Prova 3"

        /*
          Vue non rileva il cambiamento perchè stiamo modificando manualmente
          l'oggetto all'interno dell'array.Se avessimo utilizzando la funzione
          splice o se avessimo sostituito tutto l'oggetto non avremmo
          riscontrato questo problema.
        */

        // Forziamo la ri-renderizzazione del componente per vedere l'item modificato
        this.$forceUpdate()
      }
    }
  }
</script>

Con l'arrivo di Vue3 questo tipo di problema non dovrebbe più verificarsi perché il sistema che gestisce la reattività è stato completamente riscritto utilizzando dei Proxy che riescono a rilevare tutti i cambiamenti che si verificano su un oggetto.

# La soluzione definitiva: l'attributo :key

Abbiamo sicuramente già utilizzato questo attributo in altri contesti, è infatti buona norma specificarlo quando utilizziamo dei cicli for per permettere a Vue di renderizzare le liste in modo corretto, ma esistono altri casi in cui questo attributo ci torna utile. Utilizzando l'attributo :key possiamo associare il componente a una chiave e nel momento in cui essa verrà modificata Vue provvederà a distruggere a ricreare il componente.

<template>
  <b-button :key="renderKey" />
</template>

<script>
  export default {
    data() {
      return {
        renderKey: 0
      }
    }
    methods: {
      forceRerender() {
        // Incrementa il valore di renderKey in modo tale da forzare la renderizzazione
        this.renderKey++
      }
    }
  }
</script>

Tra tutte le soluzioni che abbiamo visto questa è sicuramente quella più semplice e anche la più elegante.