Fermer

août 26, 2024

Pourquoi ne pas utiliser les fonctions avec état comme méthodes Vue dans l’API Options

Pourquoi ne pas utiliser les fonctions avec état comme méthodes Vue dans l’API Options


L’utilisation de fonctions avec état sur le bloc de méthodes de l’API d’options dans Vue ne fonctionne pas comme vous pourriez vous y attendre.

Un piège courant lors du développement d’applications Vue, et l’un des plus difficiles à identifier et à déboguer ses problèmes, consiste à utiliser des fonctions avec état sur le methods bloc de l’API d’options.

Arrière-plan

Commençons par un petit historique. Une fonction avec état est une fonction qui conserve un certain état entre les exécutions. Dans la plupart des cas, ces fonctions sont des fonctions d’ordre supérieur, c’est-à-dire des fonctions qui renvoient une fonction.

Un bon exemple de fonction avec état d’ordre supérieur, et qui est à l’origine de bon nombre de ces scénarios, est celui de Lodash debounce. C’est une fonction utilitaire très pratique qui prend une fonction comme paramètre principal et renvoie une fonction anti-rebond.

Pour comprendre ce que signifie l’anti-rebond, comme l’explique la documentation de Lodash : « les retards invoquant [a function] jusqu’à après [certain] des millisecondes se sont écoulées depuis la dernière fois que la fonction anti-rebond a été invoquée. Ou, en termes plus simples, cela empêche qu’une fonction soit appelée à plusieurs reprises jusqu’à ce qu’un certain temps se soit écoulé.

Le composant exemple

Afin de mieux comprendre le problème, nous devons d’abord créer quelques composants et un exemple d’application pour démontrer le problème.

Reusable.vue

<template>
  <p>My amazing counter</p>
  <button @click="addToCounterDebounced">+</button>
  State: {{ counter }}
</template>

<script>
import { debounce } from "lodash";

export default {
  data: () => ({
    counter: 0,
  }),
  methods: {
    addToCounter() {
      this.counter++;
    },
    addToCounterDebounced: debounce(function() {
      this.counter++;
    }, 2000),
  },
};
</script>

Plongeons-y.

  1. Nous créons un bouton qui appelle un addToCounterDebounce fonction lorsque vous cliquez dessus.
  2. Le addToCounterDebounce la fonction est déclarée dans le methods bloc de l’API d’options, et appels debounce pour générer la fonction anti-rebond.
  3. Au sein du debounce appel, nous créons une fonction anonyme qui augmente la valeur du composant counter propriété par 1.
  4. Nous avons fixé un 2000 Minuterie anti-rebond en millisecondes (2 secondes) pour cette fonction, ce qui signifie que si cette fonction est appelée plusieurs fois dans les 2 secondes suivant le dernier appel, elle ne sera exécutée qu’une seule fois. Souviens-toi de ça !
  5. Enfin, nous imprimons le counter sous le bouton pour que nous puissions facilement le voir s’exécuter.

Le composant après avoir cliqué une fois dessus :

Mon incroyable compteur a un bouton plus et un champ État : 1

Même si nous cliquons sur le bouton 100 fois, l’état n’augmentera qu’une fois toutes les 2 secondes sans qu’aucune fonction ne soit appelée, c’est la nature des méthodes anti-rebond.

Si vous vous interrogez sur l’anti-rebond et quand l’utiliser, un bon cas d’utilisation des fonctions anti-rebond dans des scénarios réels consiste à retarder l’entrée de l’utilisateur lors des appels d’API.

Par exemple, un email champ de saisie qui envoie une requête ping au serveur pour vérifier si l’e-mail est déjà utilisé. Vous ne voulez probablement pas envoyer une requête ping au serveur pour chaque une seule frappe ou modification, et je préfère attendre que l’utilisateur ait fini de taper pour déclencher la méthode.

Le problème

Si vous deviez monter ce composant dans une application et l’utiliser seul, vous ne verriez probablement aucun problème. Vous pourriez même écrire des tests unitaires et tout fonctionnerait parfaitement.

Mais que se passerait-il lorsque deux instances du même composant seraient créées ?

App.vue

<template>
  <div>
    <Reusable />
    <Reusable />
  </div>
</template>
<script>

import Reusable from "./Reusable.vue";

export default {
  components: { Reusable }
};
</script>

Notez que nous avons importé le Reusable composant que nous avons créé précédemment dans notre application et en avons créé deux instances différentes sur les lignes 3 et 4. Tout devrait fonctionner de la même manière, n’est-ce pas ? Droite?

Allez-y et essayez-le. Cliquez sur both État + boutons plusieurs fois dans la fenêtre de deux secondes. Nous nous attendrions les deux États pour obtenir un +1.

L'utilisateur clique plusieurs fois sur le premier bouton, puis plusieurs fois sur le deuxième bouton. Après un battement, le second passe de 0 à 1

Hmmm… Peu importe le nombre de fois que nous cliquons sur les deux composants, seul celui sur lequel nous avons cliqué en dernier sera mis à jour. Mais pourquoi ?

Pourquoi ça ne marche pas ?

Comme nous l’avons appris au début de l’article, debounce est une fonction avec état : elle conserve l’état interne pendant que l’exécution du composant est en cours. Lorsque nous commençons à cliquer, la fonction reçoit la référence au composant cliqué counter propriété.

Cependant, lorsque l’on clique sur le deuxième bouton, le counter la référence est remplacée par celle du deuxième composant counter. La première référence est « oubliée », car la fonction attend la fin du timer pour exécuter son appel interne de this.counter++.

La solution

Heureusement, la solution est simple et constitue également une bonne pratique à conserver pour toute utilisation de fonctions avec état et de fonctions d’ordre supérieur dans les composants Vue.

Reusable.vue

<template>
  <p>My amazing counter</p>
  <button @click="addToCounter">+</button>
  State: {{ counter }}
</template>

<script>
import { debounce } from "lodash";

export default {
  data: () => ({
    counter: 0,
  }),
  mounted() {
    this.debouncedAddToCounter = debounce(function () {
      this.counter++;
    }, 2000);
  },
  methods: {
    addToCounter() {
      this.counter++;
    },
    addToCounter() {
      this.debouncedAddToCounter();
    },
  },
};
</script>

Jetons un coup d’oeil.

  1. Dans le mounted bloc, nous attribuons le résultat de notre debounce appeler un debounceAddToCounter comme propriété non réactive. Désormais, la fonction anti-rebond ne fonctionnera que dans le cadre de chaque instance.
  2. Nous renommons le addToCounterDebounced à addToCounter pour plus de clarté, et remplacez-le sur le bouton click auditeur.
  3. Dans addToCounter nous pouvons maintenant appeler this.debouncedAddToCounter.

Allez-y, essayez-le et cliquez sur les deux boutons.

Les deux boutons se mettent à jour à 1 après un battement

Désormais, nos deux compteurs sont mis à jour séparément, comme prévu.

Conclusion

Ce piège est plus courant qu’il ne devrait l’être, donc j’espère qu’après avoir lu cet article, vous serez prêt et à l’affût d’éventuels petits bugs désagréables créés par une mauvaise utilisation des fonctions avec état dans les composants Vue.

Bon codage !




Source link