v19.2Latest

Réutiliser la logique avec les Hooks personnalisés

React est livré avec plusieurs Hooks intégrés commeuseState,useContextetuseEffect. Parfois, vous souhaiteriez qu'il existe un Hook pour un objectif plus spécifique : par exemple, pour récupérer des données, pour suivre si l'utilisateur est en ligne, ou pour se connecter à une salle de discussion. Vous ne trouverez peut-être pas ces Hooks dans React, mais vous pouvez créer vos propres Hooks pour les besoins de votre application.

Vous allez apprendre
  • Ce que sont les Hooks personnalisés et comment écrire les vôtres
  • Comment réutiliser la logique entre les composants
  • Comment nommer et structurer vos Hooks personnalisés
  • Quand et pourquoi extraire des Hooks personnalisés

Hooks personnalisés : Partager la logique entre les composants

Imaginez que vous développez une application qui dépend fortement du réseau (comme la plupart des applications). Vous voulez avertir l'utilisateur si sa connexion réseau s'est accidentellement coupée pendant qu'il utilisait votre application. Comment vous y prendriez-vous ? Il semble que vous aurez besoin de deux choses dans votre composant :

  1. Un état qui suit si le réseau est en ligne.
  2. Un Effet qui s'abonne aux événements globauxonlineetoffline, et met à jour cet état.

Cela permettra à votre composant de restersynchroniséavec l'état du réseau. Vous pourriez commencer par quelque chose comme ceci :

Essayez d'activer et de désactiver votre réseau, et remarquez comment cetteStatusBarse met à jour en réponse à vos actions.

Maintenant, imaginez que vous voulezaussiutiliser la même logique dans un autre composant. Vous voulez implémenter un bouton Enregistrer qui deviendra désactivé et affichera « Reconnexion… » au lieu de « Enregistrer » lorsque le réseau est coupé.

Pour commencer, vous pouvez copier et coller l'étatisOnlineet l'Effet dansSaveButton:

Vérifiez que, si vous coupez le réseau, le bouton changera d'apparence.

Ces deux composants fonctionnent bien, mais la duplication de logique entre eux est regrettable. Il semble que même s'ils ont uneapparence visuelledifférente, vous souhaitiez réutiliser la logique entre eux.

Extraire votre propre Hook personnalisé d'un composant

Imaginez un instant que, à l'instar deuseStateetuseEffect, il existe un Hook intégréuseOnlineStatus. Alors ces deux composants pourraient être simplifiés et vous pourriez supprimer la duplication entre eux :

Bien qu'il n'existe pas un tel Hook intégré, vous pouvez l'écrire vous-même. Déclarez une fonction appeléeuseOnlineStatuset déplacez-y tout le code dupliqué des composants que vous avez écrits précédemment :

À la fin de la fonction, retournezisOnline. Cela permet à vos composants de lire cette valeur :

Vérifiez que l'activation et la désactivation du réseau mettent à jour les deux composants.

Maintenant, vos composants contiennent moins de logique répétitive.Plus important encore, le code qu'ils contient décritce qu'ils veulent faire(utiliser l'état de connexion !) plutôt quecomment le faire(en s'abonnant aux événements du navigateur).

Lorsque vous extrayez la logique dans des Hooks personnalisés, vous pouvez masquer les détails complexes de la manière dont vous interagissez avec un système externe ou une API du navigateur. Le code de vos composants exprime votre intention, pas l'implémentation.

Les noms des Hooks commencent toujours paruse

Les applications React sont construites à partir de composants. Les composants sont construits à partir de Hooks, qu'ils soient intégrés ou personnalisés. Vous utiliserez probablement souvent des Hooks personnalisés créés par d'autres, mais vous pourriez occasionnellement en écrire un vous-même !

Vous devez suivre ces conventions de nommage :

  1. Les noms des composants React doivent commencer par une majuscule,commeStatusBaretSaveButton. Les composants React doivent également retourner quelque chose que React sait afficher, comme un morceau de JSX.
  2. Les noms des Hooks doivent commencer parusesuivi d'une majuscule,commeuseState(intégré) ouuseOnlineStatus(personnalisé, comme plus haut sur la page). Les Hooks peuvent retourner des valeurs arbitraires.

Cette convention garantit que vous pouvez toujours regarder un composant et savoir où son état, ses Effets et d'autres fonctionnalités de React pourraient se « cacher ». Par exemple, si vous voyez un appel de fonctiongetColor()dans votre composant, vous pouvez être sûr qu'il ne peut pas contenir d'état React car son nom ne commence pas paruse. Cependant, un appel de fonction commeuseOnlineStatus()contiendra très probablement des appels à d'autres Hooks !

Remarque

Si votre linter estconfiguré pour React,il appliquera cette convention de nommage. Remontez dans le bac à sable ci-dessus et renommezuseOnlineStatusengetOnlineStatus. Notez que le linter ne vous permettra plus d'appeleruseStateouuseEffectà l'intérieur. Seuls les Hooks et les composants peuvent appeler d'autres Hooks !

Deep Dive
Toutes les fonctions appelées pendant le rendu doivent-elles commencer par le préfixe use ?

Les Hooks personnalisés permettent de partager une logique avec état, pas l’état lui-même

Dans l’exemple précédent, lorsque vous avez activé et désactivé le réseau, les deux composants se sont mis à jour ensemble. Cependant, il est erroné de penser qu’une seule variable d’étatisOnlineest partagée entre eux. Regardez ce code :

Cela fonctionne de la même manière qu’avant d’avoir extrait la duplication :

Ce sont deux variables d'état et deux Effets complètement indépendants ! Elles se sont trouvées avoir la même valeur au même moment parce que vous les avez synchronisées avec la même valeur externe (si le réseau est actif).

Pour mieux illustrer cela, nous avons besoin d'un exemple différent. Considérez ce composantForm :

Il y a une logique répétitive pour chaque champ de formulaire :

  1. Il y a un morceau d'état (firstNameetlastName).
  2. Il y a un gestionnaire de changement (handleFirstNameChangeethandleLastNameChange).
  3. Il y a un morceau de JSX qui spécifie les attributsvalueetonChangepour cette entrée.

Vous pouvez extraire la logique répétitive dans ce Hook personnaliséuseFormInput :

Notez qu'il ne déclarequ'uneseule variable d'état appeléevalue.

Cependant, le composantFormappelleuseFormInputdeux fois :

C'est pourquoi cela fonctionne comme la déclaration de deux variables d'état distinctes !

Les Hooks personnalisés vous permettent de partager unelogique avec étatmais pas l'état lui-même.Chaque appel à un Hook est complètement indépendant de tout autre appel au même Hook.C'est pourquoi les deux sandboxes ci-dessus sont totalement équivalentes. Si vous le souhaitez, remontez et comparez-les. Le comportement avant et après l'extraction d'un Hook personnalisé est identique.

Lorsque vous avez besoin de partager l'état lui-même entre plusieurs composants,remontez-le et passez-le vers le basà la place.

Passer des valeurs réactives entre les Hooks

Le code à l'intérieur de vos Hooks personnalisés sera ré-exécuté à chaque nouveau rendu de votre composant. C'est pourquoi, comme les composants, les Hooks personnalisésdoivent être purs.Considérez le code des Hooks personnalisés comme faisant partie du corps de votre composant !

Comme les Hooks personnalisés sont rerendus avec votre composant, ils reçoivent toujours les props et l'état les plus récents. Pour voir ce que cela signifie, considérez cet exemple de salon de discussion. Changez l'URL du serveur ou le salon de discussion :

Lorsque vous modifiezserverUrlouroomId, l'Effet« réagit » à vos changementset se resynchronise. Vous pouvez le constater grâce aux messages de la console : le chat se reconnecte chaque fois que vous modifiez les dépendances de votre Effet.

Maintenant, déplacez le code de l'Effet dans un Hook personnalisé :

Cela permet à votre composantChatRoomd'appeler votre Hook personnalisé sans se soucier de son fonctionnement interne :

Cela semble beaucoup plus simple ! (Mais cela fait la même chose.)

Notez que la logiqueréagit toujoursaux changements de props et d'état. Essayez de modifier l'URL du serveur ou la salle sélectionnée :

Remarquez comment vous prenez la valeur de retour d'un Hook :

et la passez en entrée à un autre Hook :

Chaque fois que votre composantChatRoomse re-rend, il passe les dernières valeurs deroomIdetserverUrlà votre Hook. C'est pourquoi votre Effet se reconnecte au chat chaque fois que leurs valeurs diffèrent après un re-rendu. (Si vous avez déjà travaillé avec des logiciels de traitement audio ou vidéo, enchaîner les Hooks de cette manière pourrait vous rappeler l'enchaînement d'effets visuels ou audio. C'est comme si la sortie deuseState« alimentait » l'entrée duuseChatRoom.)

Passer des gestionnaires d'événements aux Hooks personnalisés

Lorsque vous commencez à utiliseruseChatRoomdans davantage de composants, vous pourriez vouloir permettre aux composants de personnaliser son comportement. Par exemple, actuellement, la logique de ce qu'il faut faire lorsqu'un message arrive est codée en dur dans le Hook :

Imaginons que vous souhaitiez ramener cette logique dans votre composant :

Pour que cela fonctionne, modifiez votre Hook personnalisé pour qu'il accepteonReceiveMessagecomme l'une de ses options nommées :

Cela fonctionnera, mais il y a une amélioration supplémentaire que vous pouvez apporter lorsque votre Hook personnalisé accepte des gestionnaires d'événements.

Ajouter une dépendance suronReceiveMessagen'est pas idéal car cela entraînera une reconnexion du chat à chaque fois que le composant se re-rend.Encapsulez ce gestionnaire d'événements dans un Événement d'Effet pour le retirer des dépendances :

Maintenant, le chat ne se reconnectera pas à chaque fois que le composantChatRoomse re-rend. Voici une démo entièrement fonctionnelle de passage d'un gestionnaire d'événements à un Hook personnalisé avec laquelle vous pouvez expérimenter :

Remarquez que vous n'avez plus besoin de savoircommentuseChatRoomfonctionne pour l'utiliser. Vous pourriez l'ajouter à n'importe quel autre composant, passer d'autres options, et il fonctionnerait de la même manière. C'est la puissance des Hooks personnalisés.

Quand utiliser des Hooks personnalisés

Vous n'avez pas besoin d'extraire un Hook personnalisé pour chaque petit morceau de code dupliqué. Un peu de duplication est acceptable. Par exemple, extraire un HookuseFormInputpour encapsuler un simple appel àuseStatecomme précédemment est probablement inutile.

Cependant, chaque fois que vous écrivez un Effet, demandez-vous s'il serait plus clair de l'encapsuler également dans un Hook personnalisé.Vous ne devriez pas avoir besoin d'Effets très souvent,donc si vous en écrivez un, cela signifie que vous devez « sortir de React » pour vous synchroniser avec un système externe ou faire quelque chose pour lequel React n'a pas d'API intégrée. L'encapsuler dans un Hook personnalisé vous permet de communiquer précisément votre intention et comment les données y circulent.

Par exemple, considérez un composantShippingFormqui affiche deux listes déroulantes : une montre la liste des villes, et l'autre montre la liste des zones dans la ville sélectionnée. Vous pourriez commencer par un code qui ressemble à ceci :

Bien que ce code soit assez répétitif,il est correct de garder ces Effets séparés les uns des autres.Ils synchronisent deux choses différentes, donc vous ne devriez pas les fusionner en un seul Effet. Au lieu de cela, vous pouvez simplifier le composantShippingFormci-dessus en extrayant la logique commune entre eux dans votre propre HookuseData :

Maintenant, vous pouvez remplacer les deux Effets dans les composantsShippingFormpar des appels àuseData:

Extraire un Hook personnalisé rend le flux de données explicite. Vous fournissez l'urlet vous obtenez lesdata. En « cachant » votre Effet dansuseData, vous empêchez également quelqu'un travaillant sur le composantShippingFormd'y ajouter desdépendances inutiles. Avec le temps, la plupart des Effets de votre application seront dans des Hooks personnalisés.

Deep Dive
Concentrez vos Hooks personnalisés sur des cas d'utilisation concrets de haut niveau

Les Hooks personnalisés facilitent la migration vers de meilleurs modèles

Les Effets sont une« échappatoire »: vous les utilisez lorsque vous devez « sortir de React » et qu'il n'existe pas de meilleure solution intégrée pour votre cas d'usage. À terme, l'objectif de l'équipe React est de réduire au minimum le nombre d'Effets dans votre application en fournissant des solutions plus spécifiques à des problèmes plus spécifiques. Encapsuler vos Effets dans des Hooks personnalisés facilite la mise à niveau de votre code lorsque ces solutions deviennent disponibles.

Revenons à cet exemple :

Dans l'exemple ci-dessus,useOnlineStatusest implémenté avec une paire deuseStateetuseEffect.Cependant, ce n'est pas la meilleure solution possible. Elle ne prend pas en compte un certain nombre de cas limites. Par exemple, elle suppose qu'au montage du composant,isOnlineest déjàtrue, mais cela peut être faux si le réseau est déjà hors ligne. Vous pouvez utiliser l'APInavigator.onLinedu navigateur pour vérifier cela, mais l'utiliser directement ne fonctionnerait pas côté serveur pour générer le HTML initial. En bref, ce code pourrait être amélioré.

React inclut une API dédiée appeléeuseSyncExternalStorequi s'occupe de tous ces problèmes pour vous. Voici votre HookuseOnlineStatus, réécrit pour tirer parti de cette nouvelle API :

Remarquez commentvous n’avez eu besoin de modifier aucun composantpour effectuer cette migration :

C’est une autre raison pour laquelle encapsuler les Effets dans des Hooks personnalisés est souvent bénéfique :

  1. Vous rendez le flux de données vers et depuis vos Effets très explicite.
  2. Vous permettez à vos composants de se concentrer sur l’intention plutôt que sur l’implémentation exacte de vos Effets.
  3. Lorsque React ajoute de nouvelles fonctionnalités, vous pouvez supprimer ces Effets sans modifier aucun de vos composants.

À l’instar d’unsystème de design,vous pourriez trouver utile de commencer à extraire les idiomes courants des composants de votre application dans des Hooks personnalisés. Cela permettra au code de vos composants de rester concentré sur l’intention, et vous évitera d’écrire souvent des Effets bruts. De nombreux excellents Hooks personnalisés sont maintenus par la communauté React.

Deep Dive
React fournira-t-il une solution intégrée pour la récupération de données ?

Il existe plus d'une façon de le faire

Imaginons que vous souhaitiez implémenter une animation de fondu entrantà partir de zéroen utilisant l'API du navigateurrequestAnimationFrame. Vous pourriez commencer par un Effet qui configure une boucle d'animation. Pendant chaque image de l'animation, vous pourriez changer l'opacité du nœud DOM que vousdétenez dans une refjusqu'à ce qu'elle atteigne1. Votre code pourrait commencer comme ceci :

Pour rendre le composant plus lisible, vous pourriez extraire la logique dans un Hook personnaliséuseFadeIn :

Vous pourriez garder le code deuseFadeIntel quel, mais vous pourriez aussi le refactoriser davantage. Par exemple, vous pourriez extraire la logique de configuration de la boucle d'animation hors deuseFadeIndans un Hook personnaliséuseAnimationLoop :

Cependant, vous n’étiez pasobligéde le faire. Comme avec les fonctions classiques, c’est vous qui décidez en dernier ressort où tracer les frontières entre les différentes parties de votre code. Vous pourriez aussi adopter une approche très différente. Au lieu de garder la logique dans l’Effet, vous pourriez déplacer la majeure partie de la logique impérative dans uneclasse JavaScript :

Les Effets vous permettent de connecter React à des systèmes externes. Plus la coordination entre les Effets est nécessaire (par exemple, pour enchaîner plusieurs animations), plus il est logique d’extraire cette logique hors des Effets et des Hookscomplètementcomme dans le bac à sable ci-dessus. Ensuite, le code que vous avez extraitdevientle « système externe ». Cela permet à vos Effets de rester simples car ils n’ont qu’à envoyer des messages au système que vous avez déplacé hors de React.

Les exemples ci-dessus supposent que la logique de fondu doit être écrite en JavaScript. Cependant, cette animation de fondu particulière est à la fois plus simple et bien plus efficace à implémenter avec une simpleAnimation CSS :

Parfois, vous n’avez même pas besoin d’un Hook !

Récapitulatif

  • Les Hooks personnalisés vous permettent de partager de la logique entre composants.
  • Les Hooks personnalisés doivent être nommés en commençant parusesuivi d’une majuscule.
  • Les Hooks personnalisés ne partagent que la logique avec état, pas l’état lui-même.
  • Vous pouvez passer des valeurs réactives d’un Hook à un autre, et elles restent à jour.
  • Tous les Hooks se ré-exécutent à chaque nouveau rendu de votre composant.
  • Le code de vos Hooks personnalisés doit être pur, comme le code de vos composants.
  • Encapsulez les gestionnaires d’événements reçus par les Hooks personnalisés dans des Événements d’Effet.
  • Ne créez pas de Hooks personnalisés commeuseMount. Gardez leur objectif spécifique.
  • C’est à vous de décider comment et où choisir les frontières de votre code.

Try out some challenges

Challenge 1 of 5:Extract a useCounter Hook #

This component uses a state variable and an Effect to display a number that increments every second. Extract this logic into a custom Hook called useCounter. Your goal is to make the Counter component implementation look exactly like this:

export default function Counter() {
  const count = useCounter();
  return <h1>Seconds passed: {count}</h1>;
}

You’ll need to write your custom Hook in useCounter.js and import it into the App.js file.