Améliorer la réactivité de l’interface web commence souvent par optimiser la manière dont votre frontend communique avec le backend. Dans notre Semaphore UI basé sur Vue.js, nous avons introduit plusieurs techniques pour rendre le chargement des données plus rapide et plus fluide pour les utilisateurs. Cet article partage les trois changements les plus impactants que nous avons mis en œuvre dans la version 2.14.


Pourquoi optimiser l’utilisation des API ?

Les API sont souvent la colonne vertébrale des applications web dynamiques, mais des requêtes mal gérées peuvent créer des goulets d’étranglement. De longs temps d’attente pour des appels séquentiels ou des récupérations de données redondantes dégradent l’expérience utilisateur. Abordons ces problèmes de front.


1. Requêtes parallèles avec Promise.all()

Le problème : Requêtes séquentielles

Un piège courant est de faire des appels API un après l’autre. Par exemple, récupérer les détails de l’utilisateur, puis ses commandes, puis ses préférences :

// Approche séquentielle (lente 😞)
async function fetchData() {
  const user = await getUser();
  const inventory = await getInventory(projectID);
  const templates = await getTemplates(projectID);
  return { user, inventory, templates };
}

Ici, chaque requête attend que la précédente se termine. Si chaque appel prend 200 ms, le total est de 600 ms—un retard notable.

La solution : Exécution parallèle

Vue.js tire parti des capacités asynchrones de JavaScript, nous pouvons donc lancer plusieurs requêtes simultanément en utilisant Promise.all() :

// Approche parallèle (rapide ⚡)
async function fetchDataParallel() {
  const [user, inventory, templates] = await Promise.all([
    getUser(),
    getInventory(projectID),
    getTemplates(projectID),
  ]);
  return { user, inventory, templates };
}

En regroupant les requêtes indépendantes, les trois appels se résolvent simultanément. Si chacun prend 200 ms, le total tombe à 200 ms !

Intégration avec Vue

Dans un composant Vue, utilisez ce modèle dans les hooks de cycle de vie ou les méthodes :

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("Échec du chargement des données :", error);
      }
    };
  }

Astuce : Combinez cela avec des loaders squelettes pour garder les utilisateurs engagés pendant le chargement des données.


2. Réutilisation des réponses API mises en cache

Le problème : Récupération redondante

Les applications re-récupèrent souvent les mêmes données à travers les composants (par exemple, les données de profil utilisateur demandées par plusieurs vues). Cela gaspille de la bande passante et ralentit l’interface.

La solution : Mise en cache et réutilisation

Stockez les réponses API et réutilisez-les jusqu’à ce qu’elles soient invalidées. Le système de réactivité de Vue facilite cela avec un store centralisé (comme Pinia ou Vuex) :

Étape 1 : Créer un store de 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];
    },
  },
});

Étape 2 : Utiliser le Store dans les Composants

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

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

    // Réutilise l'utilisateur mis en cache ou le récupère une fois
    const user = apiCache.fetchUser();

    return { user };
  },
};

Astuce : Dans Semaphore UI, nous n’utilisons pas encore Pinia, mais nous prévoyons de migrer vers cela bientôt.

Stratégies d’invalidation du cache

3. Utilisation de loaders squelettes pour des transitions plus fluides

Le problème : UI non réactive pendant le chargement

Même avec des appels API optimisés, les utilisateurs font toujours face à de brèves attentes pendant que les données se chargent. Des écrans vides ou des interfaces gelées pendant ce temps peuvent donner à votre application un aspect peu soigné ou lent, même si le temps de récupération réel est minime.

La solution : Loaders squelettes

Les espaces réservés squelettes imitent la mise en page de votre contenu pendant le chargement des données, fournissant un retour visuel qui garde les utilisateurs engagés. Combinés avec des requêtes parallèles et la mise en cache, ils créent une perception de vitesse sans faille.


Comment ajouter des loaders squelettes dans Vue.js

Étape 1 : Choisir le composant squelette

Dans Semaphore UI, nous utilisons le framework Vuetify. Il inclut déjà un composant de loader squelette.

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

Étape 2 : Intégrer avec le chargement de données asynchrone Utilisez un état de chargement pour basculer entre les squelettes et le contenu réel :

// 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;
    }
  },
};

Étape 3 : Rendre conditionnellement les squelettes

<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>
    </div>
  </div>
</template>

Conseils avancés pour la conception de loaders

  1. Correspondre à la structure du contenu : Concevez des squelettes pour refléter votre mise en page réelle (par exemple, des espaces réservés pour les images, des lignes de texte).
  2. Prioriser le contenu au-dessus de la ligne de flottaison : Affichez d’abord les loaders pour le contenu visible pendant que les données de fond se chargent.
  3. Combiner avec la mise en cache : Si des données mises en cache existent, sautez les squelettes et montrez les données immédiatement.

Pourquoi cela compte


Mettre le tout ensemble

En combinant requêtes parallèles, mise en cache intelligente, et loaders squelettes, nous avons pu réaliser des améliorations significatives :

  1. Chargements initiaux plus rapides : Les requêtes parallèles réduisent considérablement les temps de chargement des données.
  2. Navigation plus fluide : Les réponses réutilisées réduisent les appels réseau redondants.
  3. Engagement utilisateur amélioré : Les loaders squelettes améliorent le retour visuel et la vitesse perçue.

Optimiser l’utilisation des API ne concerne pas seulement la performance brute—cela améliore également l’expérience utilisateur. Nous continuerons à affiner notre frontend pour rendre Semaphore UI plus rapide et plus convivial à chaque mise à jour.