Fermer

mai 9, 2022

Comment implémenter la mémorisation dans React pour améliorer les performances

Comment implémenter la mémorisation dans React pour améliorer les performances


Dans ce tutoriel, nous allons apprendre à implémenter la mémorisation dans React. La mémorisation améliore les performances en stockant les résultats des appels de fonction coûteux et en renvoyant ces résultats mis en cache lorsqu’ils sont à nouveau nécessaires.

Nous couvrirons les éléments suivants :

  • comment React rend l’interface utilisateur
  • pourquoi il y a un besoin de mémorisation React
  • comment nous pouvons implémenter la mémorisation pour les composants fonctionnels et de classe
  • choses à garder à l’esprit concernant la mémorisation

Cet article suppose que vous avez une compréhension de base des composants de classe et fonctionnels dans React. Si vous souhaitez approfondir ces sujets, consultez les documents officiels de React sur composants et accessoires.

Mémoïsation dans React

Comment React rend l’interface utilisateur

Avant d’entrer dans les détails de la mémorisation dans React, voyons d’abord comment React rend l’interface utilisateur à l’aide d’un DOM virtuel.

Le DOM normal contient essentiellement un ensemble de nœuds représentés sous forme d’arbre. Chaque nœud du DOM est une représentation d’un élément de l’interface utilisateur. Chaque fois qu’il y a un changement d’état dans votre application, le nœud respectif pour cet élément d’interface utilisateur et tous ses enfants sont mis à jour dans le DOM, puis l’interface utilisateur est repeinte pour refléter les modifications mises à jour.

La mise à jour des nœuds est plus rapide à l’aide d’algorithmes d’arborescence efficaces, mais la re-peinture est lente et peut avoir un impact sur les performances lorsque ce DOM contient un grand nombre d’éléments d’interface utilisateur. Par conséquent, le DOM virtuel a été introduit dans React.

Il s’agit d’une représentation virtuelle du DOM réel. Désormais, chaque fois qu’il y a un changement dans l’état de l’application, au lieu de mettre directement à jour le DOM réel, React crée un nouveau DOM virtuel. React compare ensuite ce nouveau DOM virtuel avec le DOM virtuel précédemment créé pour trouver les différences qui doivent être repeintes.

En utilisant ces différences, le DOM virtuel mettra à jour efficacement le DOM réel avec les modifications. Cela améliore les performances, car au lieu de simplement mettre à jour l’élément d’interface utilisateur et tous ses enfants, le DOM virtuel ne mettra à jour efficacement que les modifications nécessaires et minimales dans le DOM réel.

Pourquoi nous avons besoin de la mémorisation dans React

Dans la section précédente, nous avons vu comment React effectue efficacement les mises à jour du DOM en utilisant un DOM virtuel pour améliorer les performances. Dans cette section, nous examinerons un cas d’utilisation qui explique le besoin de mémorisation pour une amélioration supplémentaire des performances.

Nous allons créer une classe parent qui contient un bouton pour incrémenter une variable d’état appelée count. Le composant parent a également un appel à un composant enfant, lui transmettant un accessoire. Nous avons également ajouté console.log() instructions dans render la méthode des deux classes:


class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  handleClick = () => {
    this.setState((prevState) => {
      return { count: prevState.count + 1 };
    });
  };

  render() {
    console.log("Parent render");
    return (
      <div className="App">
        <button onClick={this.handleClick}>Increment</button>
        <h2>{this.state.count}</h2>
        <Child name={"joe"} />
      </div>
    );
  }
}

export default Parent;

Le code complet de cet exemple est disponible sur CodeSandbox.

Nous allons créer un Child classe qui accepte un accessoire passé par le composant parent et l’affiche dans l’interface utilisateur :


class Child extends React.Component {
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
}

export default Child;

Chaque fois que nous cliquons sur le bouton dans le composant parent, la valeur de comptage change. Puisqu’il s’agit d’un changement d’état, la méthode de rendu du composant parent est appelée.

Les accessoires passés à la classe enfant restent les mêmes pour chaque rendu parent, de sorte que le composant enfant ne doit pas être rendu à nouveau. Pourtant, lorsque nous exécutons le code ci-dessus et que nous continuons à incrémenter le nombre, nous obtenons le résultat suivant :

Parent render
Child render
Parent render
Child render
Parent render
Child render

Vous pouvez incrémenter vous-même le nombre pour l’exemple ci-dessus dans le bac à sable suivant et voir la console pour le résultat :


À partir de cette sortie, nous pouvons voir que, lorsque le composant parent restitue, il restitue également le composant enfant, même lorsque les accessoires transmis au composant enfant sont inchangés. Cela amènera le DOM virtuel de l’enfant à effectuer une vérification des différences avec le DOM virtuel précédent. Comme nous n’avons aucune différence dans le composant enfant – car les accessoires sont les mêmes pour tous les rendus – le vrai DOM n’est pas mis à jour.

Nous avons un avantage en termes de performances lorsque le DOM réel n’est pas mis à jour inutilement, mais nous pouvons voir ici que, même lorsqu’il n’y a pas eu de changement réel dans le composant enfant, le nouveau DOM virtuel a été créé et une vérification des différences a été effectuée. Pour les petits composants React, ces performances sont négligeables, mais pour les gros composants, l’impact sur les performances est significatif. Pour éviter ce nouveau rendu et cette vérification virtuelle du DOM, nous utilisons la mémorisation.

Mémoïsation dans React

Dans le contexte d’une application React, la mémorisation est une technique dans laquelle, chaque fois que le composant parent se restitue, le composant enfant se restitue uniquement s’il y a un changement dans les accessoires. S’il n’y a pas de changement dans les accessoires, il n’exécutera pas la méthode de rendu et renverra le résultat mis en cache. Étant donné que la méthode de rendu n’est pas exécutée, il n’y aura pas de création de DOM virtuel ni de vérification des différences, ce qui nous donnera une amélioration des performances.

Voyons maintenant comment implémenter la mémorisation dans la classe et les composants React fonctionnels pour éviter ce nouveau rendu inutile.

Implémentation de la mémorisation dans un composant de classe

Pour implémenter la mémorisation dans un composant de classe, nous utiliserons React.PureComponent. React.PureComponent met en oeuvre devraitComponentUpdate()qui effectue une comparaison superficielle sur l’état et les accessoires et rend le composant React uniquement s’il y a un changement dans les accessoires ou l’état.

Remplacez le composant enfant par le code indiqué ci-dessous :


class Child extends React.PureComponent { 
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
}

export default Child;

Le code complet de cet exemple est affiché dans le bac à sable suivant :


Le composant parent reste inchangé. Maintenant, lorsque nous incrémentons le nombre dans le composant parent, la sortie dans la console est la suivante :

Parent render
Child render
Parent render
Parent render

Pour le premier rendu, il appelle les méthodes de rendu des composants parent et enfant.

Pour un nouveau rendu ultérieur à chaque incrément, seul le composant parent render fonction est appelée. Le composant enfant n’est pas rendu à nouveau.

Implémentation de la mémorisation dans un composant fonctionnel

Pour implémenter la mémorisation dans les composants React fonctionnels, nous utiliserons React.memo().React.memo() est un composant d’ordre supérieur (HOC) qui fait un travail similaire à PureComponentévitant les re-rendus inutiles.

Voici le code d’un composant fonctionnel :


export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h2>{props.name}</h2>
    </div>
  );
}

export default React.memo(Child); 

Nous convertissons également le composant parent en composant fonctionnel, comme indiqué ci-dessous :


export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };
  console.log("Parent render");
  return (
    <div>
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} />
    </div>
  );
}

Le code complet de cet exemple est visible dans le bac à sable suivant :


Désormais, lorsque nous incrémentons le nombre dans le composant parent, ce qui suit est affiché sur la console :

Parent render
Child render
Parent render
Parent render
Parent render

Le problème avec React.memo() pour les accessoires de fonction

Dans l’exemple ci-dessus, nous avons vu que lorsque nous utilisions React.memo() HOC pour le composant enfant, le composant enfant n’a pas été restitué, même si le composant parent l’a fait.

Une petite mise en garde à prendre en compte, cependant, est que si nous passons une fonction comme accessoire au composant enfant, même après avoir utilisé React.memo(), le composant enfant sera restitué. Voyons un exemple de cela.

Nous allons changer le composant parent comme indiqué ci-dessous. Ici, nous avons ajouté une fonction de gestionnaire que nous transmettrons au composant enfant en tant qu’accessoires :


export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const handler = () => {
    console.log("handler");    
  };

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}

Le code du composant enfant reste tel quel. Nous n’utilisons pas la fonction que nous avons transmise comme accessoire dans le composant enfant :


export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h2>{props.name}</h2>
    </div>
  );
}

export default React.memo(Child);

Désormais, lorsque nous incrémentons le nombre dans le composant parent, il restitue et restitue également le composant enfant, même s’il n’y a aucun changement dans les accessoires passés.

Alors, qu’est-ce qui a poussé l’enfant à re-rendre? La réponse est que, chaque fois que le composant parent est restitué, une nouvelle fonction de gestionnaire est créée et transmise à l’enfant. Maintenant, étant donné que la fonction de gestionnaire est recréée à chaque nouveau rendu, l’enfant, sur une comparaison superficielle des accessoires, constate que la référence du gestionnaire a changé et restitue le composant enfant.

Dans la section suivante, nous verrons comment résoudre ce problème.

useCallback() pour éviter un nouveau rendu

Le principal problème à l’origine du nouveau rendu de l’enfant est la recréation de la fonction de gestionnaire, qui a modifié la référence transmise à l’enfant. Donc, nous devons avoir un moyen d’éviter cette récréation. Si le gestionnaire n’est pas recréé, la référence au gestionnaire ne changera pas, de sorte que l’enfant ne sera pas restitué.

Pour éviter de recréer la fonction à chaque fois que le composant parent est rendu, nous utiliserons un crochet React appelé useCallback(). Les crochets ont été introduits dans React 16. Pour en savoir plus sur les crochets, vous pouvez consulter le site officiel de React documentation sur les crochetsou consultez « React Hooks : Comment démarrer et créer le vôtre”.

Le useCallback() hook prend deux arguments : la fonction de rappel et une liste de dépendances.

Considérez l’exemple suivant de useCallback():

const handleClick = useCallback(() => {
  
}, [x,y]);

Ici, useCallback() est ajouté à la handleClick() une fonction. Le deuxième argument [x,y] peut être un tableau vide, une seule dépendance ou une liste de dépendances. Chaque fois qu’une dépendance mentionnée dans le deuxième argument change, alors seulement le handleClick() fonction soit recréée.

Si les dépendances mentionnées dans useCallback() ne changez pas, une version mémorisée du rappel mentionné comme premier argument est renvoyée. Nous allons changer notre composant fonctionnel parent pour utiliser le useCallback() crochet pour le gestionnaire qui est passé au composant enfant :


export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const handler = useCallback(() => { 
    console.log("handler");
  }, []);

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}

Le code du composant enfant reste tel quel.

Le code complet de cet exemple est illustré ci-dessous :


Lorsque nous incrémentons le nombre dans le composant parent pour le code ci-dessus, nous pouvons voir la sortie suivante :

Parent render
Child render
Parent render
Parent render
Parent render

Puisque nous avons utilisé le useCallback() crochet pour le gestionnaire parent, chaque fois que le parent restitue, la fonction du gestionnaire ne sera pas recréée et une version mémorisée du gestionnaire est envoyée à l’enfant. Le composant enfant effectuera une comparaison superficielle et remarquera que la référence de la fonction de gestionnaire n’a pas changé – il n’appellera donc pas le render méthode.

Choses à retenir

La mémorisation est une bonne technique pour améliorer les performances des applications React en évitant les rendus inutiles d’un composant si ses accessoires ou son état n’ont pas changé. Vous pourriez penser à ajouter simplement la mémorisation pour tous les composants, mais ce n’est pas une bonne façon de construire vos composants React. Vous ne devez utiliser la mémorisation que dans les cas où le composant :

  • renvoie la même sortie lorsqu’on lui donne les mêmes accessoires
  • a plusieurs éléments d’interface utilisateur et une vérification DOM virtuelle aura un impact sur les performances
  • est souvent fourni les mêmes accessoires

Conclusion

Dans ce tutoriel, nous avons vu :

  • comment React rend l’interface utilisateur
  • pourquoi la mémorisation est nécessaire
  • comment mettre en œuvre la mémorisation dans React via React.memo() pour un composant React fonctionnel et React.PureComponent pour un composant de classe
  • un cas d’utilisation où, même après avoir utilisé React.memo()le composant enfant restituera
  • comment utiliser le useCallback() crochet pour éviter le re-rendu lorsqu’une fonction est transmise en tant qu’accessoire à un composant enfant.

J’espère que vous avez trouvé cette introduction à la mémorisation de React utile !




Source link