Optimisation Granulaire avec React.memo et useCallback

9 min de lecture

1. Introduction à React.memo et useCallback

Avec la croissance constante des applications React en taille et en complexité, l'optimisation des performances est devenue primordiale. Deux outils efficaces mais parfois sous-exploités sont React.memo et useCallback.

1.1 Principe et Usage de React.memo

Le React.memo est une fonction d'ordre supérieur (HOF) qui mémorise le résultat de rendu d'un composant si ses accessoires sont les mêmes qu'au rendu précédent. Ceci permet d'éviter des rendus inutiles pour améliorer les performances.

1const MyComponent = React.memo(function MyComponent(props) {
2 /* render using props */
3});

Dans cet exemple, le composant MyComponent ne sera pas re-rendu si les accessoires restent les mêmes.

1.2 Principe et Usage de useCallback

Le useCallback est un crochet qui peut être utilisé pour mémoriser une fonction de rappel entre les rendus. Il est particulièrement utile lorsque vous passez des fonctions de rappel à des composants fortement imbriqués.

1const memoizedCallback = useCallback(
2 () => {
3 doSomething(a, b);
4 },
5 [a, b],
6);

Dans ce cas, la fonction de rappel n'est recréée que si les valeurs de a ou b changent.

1.3 Les bénéfices potentiels pour les Performances

L'usage de React.memo et useCallback permet de réduire le travail nécessaire pour re-rendre des composants. Cela peut conduire à une réduction significative du temps de rendu et améliorer la fluidité de l'interaction utilisateur.

Note : Ces techniques d'optimisation ne sont pas une panacée. L'usage de React.memo et useCallback peut avoir un coût car elles ajoutent une surcharge de mémorisation. Il est donc recommandé de mesurer l'impact sur les performances avant et après leur mise en place pour s'assurer de leur utilité.

En conclusion, React.memo et useCallback peuvent être des outils précieux pour l'optimisation des performances dans les applications React. Cependant, leur utilité réelle dépend des circonstances spécifiques et d'une bonne compréhension de leur fonctionnement. Les sections suivantes approfondiront ces aspects et permettront une utilisation plus appropriée de ces outils.

2. Approfondissement sur React.memo

2.1 Comment React.memo fonctionne-t-il ?

React.memo est une fonction d'ordre supérieur. Elle prend un composant et retourne un composant mémorisé qui peut prévenir le rendu inutile en comparant les accessoires.

Remarque : Par défaut, React.memo comparer shagnostiquement chaque champ de l'objet accessoires comme le ferait Object.is. Cependant, une fonction de comparaison personnalisée peut être fournie en second argument à React.memo pour changer cette comparaison par défaut.

L'utilisation classique est la suivante :

1function ComponentName(props) { /* render logic using props */ }
2export default React.memo(ComponentName);

Dans cet exemple, React.memo empêche le rendu de ComponentName à moins que ses accessoires n'aient changé.

2.2 Exemples d'usage de React.memo

  1. Prévenir le re-rendu de composants non concernés par une mise à jour d'état: Parfois, une mise à jour d'état d'un composant parent entraîne le rendu des composants enfants même si les accessoires reçus par les enfants n'ont pas changé.

  2. Gestion des tables de données volumineuses: Si une application contient une table de données énorme avec des dizaines de colonnes et des milliers de lignes, rendre toutes ces données à chaque fois peut ralentir l'application. React.memo peut rendre uniquement les lignes ou les cellules modifiées et diminuer ainsi le temps d'affichage.

  3. Optimisation des listes: S'il existe un composant qui rend une longue liste, chaque modification mineure de cette liste entraîne le rendu de l'ensemble de la liste. L'emploi de React.memo peut aider à rendre uniquement les éléments modifiés de la liste.

Important : l'utilisation de React.memo nécessite un benchmarking avant et après l'optimisation pour vérifier si elle apporte les améliorations de performance attendues.

2.3 Quand utiliser React.memo

React.memo devrait être utilisé quand :

  • Le rendu d'un composant est coûteux et le composant est fréquemment re-rendu avec les mêmes accessoires.
  • Un composant est re-rendu par des changements d'état qui n'affectent pas effectivement le composant.
  • Une large partie de l'arbre de composants n'a pas besoin d'être re-rendu du fait des changements d'état ou des accessoires de quelques composants.

À savoir : l'utilisation de React.memo peut également avoir des inconvénients. Il peut introduire des frais généraux supplémentaires pour le processus de comparaison des accessoires. Par conséquent, son utilisation devrait être soigneusement considérée.

3. Approfondissement sur useCallback

3.1 Comment useCallback fonctionne-t-il ?

useCallback est un Hook qui permet de mémoriser une fonction de rappel entre les rendus d'un composant. La syntaxe de useCallback est la suivante :

1const memoizedCallback = useCallback(() => {
2 doSomething(a, b);
3}, [a, b]);

Dans cet exemple, la fonction de rappel est mémorisée jusqu'à ce que a ou b change.

3.2 Exemples d'usage de useCallback

Voici quelques cas où useCallback peut se révéler bénéfique :

  1. Passation des fonctions de rappel à des composants fortement imbriqués : Si un composant parent fournit une fonction de rappel à un composant enfant à l'aide d'accessoires et que cette fonction ne doit pas changer entre les rendus, useCallback peut être utilisé pour mémoriser la fonction de rappel.
  2. Rendu coûteux : Si le rendu d'un composant est coûteux, et qu'une fonction à l'intérieur de ce composant peut déclencher ce rendu, utiliser useCallback pour mémoriser la fonction peut aider à réduire le coût.
  3. Dépendances des effets : Parfois, une fonction est une dépendance d'un effet (useEffect, useMemo, etc.). Pour éviter que l'effet ne se déclenche chaque fois que le composant est rendu, on peut utiliser useCallback pour mémoriser la fonction.

3.3 Quand utiliser useCallback ?

useCallback devrait être utilisé lorsque :

  • Vous passez une fonction en tant qu'accessoire à un composant enfant pour éviter les rendus inutiles de ce composant.
  • Vous utilisez une fonction à l'intérieur d'un effet (useEffect, useMemo, etc.) comme une dépendance et que vous ne voulez pas que l'effet se déclenche à chaque rendu.

Important : Comme pour React.memo, le coût de la mémorisation peut parfois annuler les bénéfices apportés par useCallback, en particulier si la création de la fonction de rappel est peu coûteuse et qu'elle ne déclenche pas un grand nombre de rendus en aval. L'utilisation de useCallback devrait donc être mesurée avec soin.

4. Éviter les pièges courants

4.1 Comprendre l'égalité référentielle en JavaScript

L'égalité référentielle est un concept crucial à comprendre lors de l'utilisation de React.memo et useCallback. Cette notion se base sur l'identité des objets et non leur structure.

En JavaScript, deux objets ou deux tableaux sont considérés comme égaux uniquement s'ils font référence au même objet, même si leurs contenus sont identiques. Par exemple :

1const a = {x: 1, y: 2};
2const b = {x: 1, y: 2};
3
4console.log(a === b); // résultat : false

Dans cet exemple, malgré le fait que a et b ont le même contenu, ils sont considérés comme différents aux yeux de JavaScript car ils ne font pas référence au même objet. Le concept d'égalité référentielle est essentiel pour comprendre comment React.memo et useCallback déterminent si le re-rendering est nécessaire ou non.

4.2 Les erreurs courantes et comment les éviter

  • Création d'objets ou de tableaux dans les accessoires : Ceci peut déclencher des re-renderings inutiles car chaque objet ou tableau créé a une nouvelle référence.

    Solution : Stocker ces objets ou tableaux dans l'état du composant ou utiliser useMemo pour une création conditionnelle.

  • Utilisation de fonctions dans les accessoires : Des situations comparables se produisent avec les fonctions. Une nouvelle référence est créée à chaque rendu, ce qui déclenche inutilement un re-rendering.

    Solution : Utiliser useCallback pour mémoriser les fonctions.

  • Manque de dépendances dans le tableau de dépendances de useCallback: Ce cas peut entraîner des comportements inattendus car la fonction pourrait ne pas utiliser les valeurs les plus récentes des variables.

    Solution : S'assurer que toutes les variables utilisées dans la fonction de rappel sont incluses dans le tableau de dépendances.

Attention : Le coût de la mémorisation avec useCallback ou React.memo peut annuler les économies de performances dans certains cas, notamment si les fonctions ou composants sont peu coûteux à générer et ne déclenchent pas de nombreux re-rendering. Leur utilisation doit toujours être mesurée avec soin.

5. Études de cas et solutions

5.1 Cas d'une liste de Composants

Imaginons que nous avons une liste de tâches avec chaque tâche représentée par un composant Task. Chaque fois qu'une tâche est mise à jour, tous les composants Task sont re-rendus, ce qui peut conduire à des problèmes de performance.

Solution : Utiliser React.memo pour éviter les re-rendus inutiles. Chaque composant Task sera re-rendu seulement si les props qu'il reçoit changent. C'est-à-dire, si la Task en question est mise à jour.

Exemple de code :

1const Task = React.memo(function Task({ task, updateTask }) {
2 // Logic du rendu du composant
3});
4
5function TaskList({ tasks, updateTask }) {
6 return tasks.map(task => <Task key={task.id} task={task} updateTask={updateTask} />);
7}

Ainsi, seules les tâches modifiées seront re-rendues. Il est important de noter que la fonction updateTask doit être mémorisée avec useCallback pour garantir une référence constante et éviter les re-rendus inutiles.

5.2 Cas d'un Composant avec de nombreux props

Supposons que nous avons un composant UserProfile qui reçoit de nombreux props. Chaque fois qu'un seul prop est modifié, le composant entier est re-rendu.

Solution : Créer des composants plus petits pour chaque prop et les mémoriser en utilisant React.memo. Chaque composant ne sera re-rendu que si le prop qu'il reçoit est modifié.

Voici un exemple de comment structurer le code :

1const UserName = React.memo(function UserName({ name }) {
2 // Logic du rendu du nom de l'utilisateur
3});
4const UserAge = React.memo(function UserAge({ age }) {
5 // Logic du rendu de l'âge de l'utilisateur
6});
7
8function UserProfile({ user }) {
9 // Le composant UserProfile se rend uniquement lorsque le prop user change
10 return (
11 <div>
12 <UserName name={user.name} />
13 <UserAge age={user.age} />
14 {/* etc pour les autres props */}
15 </div>
16 );
17}

Dans cet exemple, chaque sous-composant ne sera re-rendu que si le prop spécifique associé à ce composant est modifié. Cela permet de réduire le nombre de re-rendus inutiles lorsque les props changent.

Remarque : Il est important de noter que la création de nombreux sous-composants et l'utilisation de React.memo peut introduire une surcharge de mémorisation et augmenter la complexité du code. Par conséquent, cette méthode doit être utilisée judicieusement et en fonction des exigences spécifiques du projet.

6. Comparaison avec d'autres techniques d'optimisation

6.1 React.PureComponent et shouldComponentUpdate

React.PureComponent effectue une comparaison superficielle de chaque champ de l'objet des accessoires et de l'état. Si une modification se produit à n'importe quel niveau de l'objet, le composant sera re-rendu. React.memo offre une flexibilité supplémentaire en ce sens qu'il permet l'usage d'une fonction de comparaison personnalisée.

shouldComponentUpdate est une méthode de cycle de vie des composants de classe qui permet un contrôle granulaire sur le processus de re-rendu. Elle exige cependant une programmation plus délicate et une connaissance approfondie du composant. De plus, elle n'est pas utilisable avec les composants fonctionnels utilisés dans Hooks.

React.memoReact.PureComponentshouldComponentUpdate
Flexibilité de comparaisonHaute (fonction de comparaison personnalisable)Modérée (comparaison superficielle des accessoires et de l'état)Haute (comparaison personnalisable)
Facilité d'usageHauteMmodéréeFaible (requiert plus de programmation)
Compatibilité avec HooksOuiNonNon

Note : Les performances dépendent spécifiquement du cas. Il est recommandé de procéder à un benchmarking avant et après l'optimisation pour confirmer l'efficacité.

6.2 React.useMemo et React.useEffect

useMemo est similaire à useCallback, mais pour les valeurs calculées coûteuses. useMemo récupère le résultat du calcul entre les rendus jusqu'à ce que les dépendances changent.

useEffect est utilisé pour effectuer des effets secondaires dans les composants et n'offre pas de mémorisation. Cependant, il peut avoir des dépendances (comme une fonction provenant de useCallback), et il se déclenchera seulement quand ces dépendances changent.

useCallbackuseMemouseEffect
Objectif principalMémoriser les fonctions de rappelMémoriser les valeurs calculéesExécuter les effets secondaires
Rendu basé sur des dépendancesOuiOuiOui

Remarque : Il convient de noter que l'usage de ces Hooks peut également avoir un coût, principalement dû à la surcharge de mémorisation. Leur utilisation devrait donc être dépendante du cas d'utilisation (source).

7. Conseils pour la mise en œuvre pratique

7.1 Comment choisir la bonne stratégie?

Pour parvenir à une optimisation granulaire, il est nécessaire de comprendre votre système et de bien bénéficier de ces techniques d'optimisation. Quelques facteurs à considérer incluent:

  • Le type de composants : Les composants de classe sont plus optimisés avec shouldComponentUpdate ou React.PureComponent, tandis que les composants fonctionnels fonctionnent bien avec React.memo, useCallback ou useMemo.

  • La fréquence des mises à jour : Pour les composants mis à jour à une fréquence relativement basse, les techniques d'optimisation peuvent ne pas apporter d'amélioration de performance notable et peuvent même ajouter une surcharge inutile. Cependant, pour des composants à mise à jour fréquente, ces techniques peuvent aider à améliorer les performances.

  • Le nombre de composants enfants : Les composants ayant un grand nombre de composants enfants peuvent bénéficier plus de useCallback et React.memo pour éviter les re-rendus inutiles.

7.2 Mesurer l'impact sur les performances

Avant et après la mise en œuvre de ces techniques, il est essentiel d'évaluer l'efficacité de l'optimisation. Les outils tels que le Profiling add-on pour React DevTools peuvent fournir des informations précieuses sur les temps de rendu des composants.

7.3 Comment combiner ces méthodes pour un effet maximum ?

Il n'y a pas de solution unique à tous les scénarios d'optimisation. L'efficacité de React.memo, useCallback, et autres techniques dépend des spécificités du projet. Les bonnes pratiques comprennent:

  • Combiner React.memo et useCallback : Quand vous passez des fonctions de rappel à un composant enfant via les accessoires, utiliser React.memo sur le composant enfant et useCallback pour mémoriser la fonction de rappel.

  • Utiliser useMemo avec des objets coûteux : Quand vous avez des objets coûteux à calculer et que vous voulez éviter de les recréer à chaque rendu, useMemo est pratique.

  • Employer useEffect judicieusement : useEffect est utile pour gérer les effets secondaires et peut être optimisé en ajustant son tableau de dépendances.

En conclusion, ces techniques d'optimisation fournissent des outils puissants pour améliorer les performances des applications React. Cependant, la clé est de comprendre les mécanismes sous-jacents, de mesurer leur impact sur les performances, et d'effectuer des ajustements basés sur les résultats spécifiques du projet.

8. Réflexions finales sur l'optimisation des performances

8.1 L'optimisation précoce est le mal

On dit souvent en développement logiciel que l'optimisation prématurée est la cause de tous les maux. C'est particulièrement vrai en React. Avant d'optimiser quoi que ce soit, assurez-vous que vous avez réellement un problème de performance. Utilisez des profilers pour identifier les goulots d'étranglement de performance spécifiques. Ensuite, choisissez les techniques d'optimisation appropriées pour ces problèmes spécifiques.

8.2 Le rôle de l'évolution des pratiques

L'écosystème JavaScript et React évolue rapidement. Les meilleures pratiques d'aujourd'hui peuvent devenir obsolètes demain à mesure que de nouveaux outils et techniques sont introduits. Il est important de se tenir informé et de continuer à apprendre.

5.0 (33 notes)

Cet article vous a été utile ? Notez le