Migliorare la reattività dell’interfaccia web spesso inizia con l’ottimizzazione di come il frontend comunica con il backend. Nella nostra Semaphore UI basata su Vue.js, abbiamo introdotto diverse tecniche per rendere il caricamento dei dati più veloce e fluido per gli utenti. Questo post condivide i tre cambiamenti più impattanti che abbiamo implementato nella versione 2.14.


Perché ottimizzare l’uso delle API?

Le API sono spesso la spina dorsale delle app web dinamiche, ma richieste gestite male possono creare colli di bottiglia. Lunghi tempi di attesa per chiamate sequenziali o recupero di dati ridondanti degradano l’esperienza utente. Affrontiamo questi problemi direttamente.


1. Richieste parallele con Promise.all()

Il problema: Richieste sequenziali

Un errore comune è fare chiamate API una dopo l’altra. Ad esempio, recuperare i dettagli dell’utente, poi i loro ordini, poi le loro preferenze:

// Approccio sequenziale (lento 😞)
async function fetchData() {
  const user = await getUser();
  const inventory = await getInventory(projectID);
  const templates = await getTemplates(projectID);
  return { user, inventory, templates };
}

Qui, ogni richiesta aspetta che la precedente finisca. Se ogni chiamata richiede 200ms, il totale è 600ms—un ritardo notevole.

La soluzione: Esecuzione parallela

Vue.js sfrutta le capacità asincrone di JavaScript, quindi possiamo inviare più richieste simultaneamente usando Promise.all():

// Approccio parallelo (veloce ⚡)
async function fetchDataParallel() {
  const [user, inventory, templates] = await Promise.all([
    getUser(),
    getInventory(projectID),
    getTemplates(projectID),
  ]);
  return { user, inventory, templates };
}

Raggruppando richieste indipendenti, tutte e tre le chiamate si risolvono contemporaneamente. Se ognuna richiede 200ms, il totale scende a 200ms!

Integrazione con Vue

In un componente Vue, usa questo schema nei lifecycle hooks o nei metodi:

export default {
  props: {
    projectID: Number,
  },
  watch: {
    async projectID() {
      await this.fetchDataParallel();
    },
  },
  async created() {
    await this.fetchDataParallel();
  },
  methods: {
    async fetchDataParallel() {
      try {
        const [
          this.user, 
          this.inventory,
          this.templates
        ] = await Promise.all([
          getUser(),
          getInventory(this.projectID),
          getTemplates(this.projectID),
        ]);
      } catch (error) {
        console.error("Errore nel caricamento dei dati:", error);
      }
    };
  }

Suggerimento: Combina questo con i caricamenti scheletrici per mantenere gli utenti coinvolti mentre i dati vengono caricati.


2. Riutilizzo delle risposte API memorizzate

Il problema: Recupero ridondante

Le app spesso recuperano nuovamente gli stessi dati tra i componenti (ad esempio, i dati del profilo utente richiesti da più viste). Questo spreca larghezza di banda e rallenta l’interfaccia.

La soluzione: Memorizzare e riutilizzare

Memorizza le risposte API e riutilizzale fino a quando non vengono invalidate. Il sistema di reattività di Vue rende questo facile con uno store centralizzato (come Pinia o Vuex):

Passo 1: Crea uno store di cache

// stores/apiCache.js
import { defineStore } from 'pinia';

export const useApiCache = defineStore('apiCache', {
  state: () => ({
    project: null,
    templates: {},
  }),
  actions: {
    async fetchProject() {
      if (!this.project) {
        this.project = await getProject();
      }
      return this.project;
    },
    async fetchTemplates(id) {
      if (!this.templates[id]) {
        this.templates[id] = await fetchTemplates(id);
      }
      return this.templates[id];
    },
  },
});

Passo 2: Usa lo Store nei Componenti

// Component.vue
import { useApiCache } from '@/stores/apiCache';

export default {
  setup() {
    const apiCache = useApiCache();

    // Riutilizza l'utente memorizzato o recupera una sola volta
    const user = apiCache.fetchUser();

    return { user };
  },
};

Suggerimento: Nella Semaphore UI, non usiamo ancora Pinia, ma prevediamo di migrare a essa presto.

Strategie di invalidazione della cache

3. Utilizzo di caricamenti scheletrici per transizioni più fluide

Il problema: UI non reattiva durante il caricamento

Anche con chiamate API ottimizzate, gli utenti affrontano ancora brevi ritardi mentre i dati vengono caricati. Schermi vuoti o interfacce bloccate durante questo tempo possono far sembrare la tua app poco rifinita o lenta, anche se il tempo di recupero effettivo è minimo.

La soluzione: Caricamenti scheletrici

I segnaposto scheletrici imitano il layout del tuo contenuto mentre i dati vengono caricati, fornendo un feedback visivo che mantiene gli utenti coinvolti. Combinati con richieste parallele e caching, creano una percezione di velocità senza soluzione di continuità.


Come aggiungere caricamenti scheletrici in Vue.js

Passo 1: Scegli il componente scheletrico

Nella Semaphore UI utilizziamo il framework Vuetify. Include già un componente di caricamento scheletrico.

<v-skeleton-loader
  type="
    table-heading,
    image,
    list-item-two-line,
    list-item-two-line,
    list-item-two-line"
></v-skeleton-loader>

Passo 2: Integra con il recupero di dati asincroni Usa uno stato di caricamento per alternare tra scheletri e contenuto reale:

// Component.vue
export default {
  data() {
    return {
      isLoading: true,
      user: null,
      templates: [],
    };
  },
  async created() {
    this.isLoading = true;
    try {
      [this.user, this.templates] = await Promise.all([
        fetchUser(),
        fetchTemplates(),
      ]);
    } finally {
      this.isLoading = false;
    }
  },
};

Passo 3: Rendi condizionalmente i caricamenti scheletrici

<template>
  <div class="user-profile">
    <v-skeleton-loader
      v-if="isLoading"
      type="
        table-heading,
        image,
        list-item-two-line,
        list-item-two-line,
        list-item-two-line"
    ></v-skeleton-loader>
    <v-card v-else>
      <h2>{{ user.name }}</h2>

      <v-card v-for="tpl in templates" :key="tpl.id">
        <v-card-title>{{ tpl.name }}</v-card-title>
        <v-card-text>{{ tpl.description }}</v-card-text>
      </v-card>
    </v-card>
  </div>
</template>

Suggerimenti avanzati per il design dei caricamenti

  1. Corrispondenza della struttura del contenuto: Progetta i caricamenti scheletrici per rispecchiare il tuo layout reale (ad esempio, segnaposto per immagini, righe di testo).
  2. Dai priorità ai contenuti sopra la piega: Mostra i caricamenti per i contenuti visibili prima mentre i dati di sfondo finiscono.
  3. Combina con il caching: Se i dati memorizzati esistono, salta i caricamenti scheletrici e mostra i dati immediatamente.

Perché questo è importante


Mettere tutto insieme

Combinando richieste parallele, caching intelligente e caricamenti scheletrici, siamo riusciti a ottenere miglioramenti significativi:

  1. Caricamenti iniziali più veloci: Le richieste parallele hanno ridotto significativamente i tempi di caricamento dei dati.
  2. Navigazione più fluida: Le risposte riutilizzate hanno ridotto le chiamate di rete ridondanti.
  3. Maggiore coinvolgimento degli utenti: I caricamenti scheletrici hanno migliorato il feedback visivo e la velocità percepita.

Ottimizzare l’uso delle API non riguarda solo le prestazioni grezze—migliora anche l’esperienza utente. Continueremo a perfezionare il nostro frontend per rendere Semaphore UI più veloce e amichevole con ogni aggiornamento.