Crochets de réaction utiles que vous pouvez utiliser dans vos projets

Publié: 2022-03-10
Résumé rapide ↬ Les composants basés sur les classes React sont désordonnés, déroutants, difficiles pour les humains et les machines. Mais avant React 16.8, les composants basés sur les classes étaient obligatoires pour tous les projets nécessitant des états, des méthodes de cycle de vie et de nombreuses autres fonctionnalités importantes. Tout cela a changé avec l'introduction de crochets dans React 16.8. Les crochets changent la donne. Ils ont simplifié React, l'ont rendu plus propre, plus facile à écrire et à déboguer, et ont également réduit la courbe d'apprentissage.

Les crochets sont simplement des fonctions qui vous permettent de vous connecter ou d'utiliser les fonctionnalités de React. Ils ont été introduits lors de la React Conf 2018 pour résoudre trois problèmes majeurs des composants de classe : l'enfer du wrapper, les composants énormes et les classes déroutantes. Les crochets donnent de la puissance aux composants fonctionnels de React, permettant de développer une application entière avec eux.

Les problèmes susmentionnés de composants de classe sont liés et résoudre l'un sans l'autre pourrait introduire d'autres problèmes. Heureusement, les crochets ont résolu tous les problèmes simplement et efficacement tout en laissant de la place pour des fonctionnalités plus intéressantes dans React. Les hooks ne remplacent pas les concepts et les classes React déjà existants, ils fournissent simplement une API pour y accéder directement.

L'équipe React a introduit plusieurs crochets dans React 16.8. Cependant, vous pouvez également utiliser des crochets de fournisseurs tiers dans votre application ou même créer un crochet personnalisé. Dans ce didacticiel, nous allons examiner quelques crochets utiles dans React et comment les utiliser. Nous passerons en revue plusieurs exemples de code de chaque hook et explorerons également comment créer un hook personnalisé.

Remarque : Ce didacticiel nécessite une compréhension de base de Javascript (ES6+) et de React.

Plus après saut! Continuez à lire ci-dessous ↓

Motivation derrière les crochets

Comme indiqué précédemment, les crochets ont été créés pour résoudre trois problèmes : l'enfer des wrappers, les composants volumineux et les classes déroutantes. Jetons un coup d'œil à chacun d'entre eux plus en détail.

L'enfer de l'emballage

Les applications complexes construites avec des composants de classe s'exécutent facilement dans l'enfer des wrappers. Si vous examinez l'application dans React Dev Tools, vous remarquerez des composants profondément imbriqués. Cela rend très difficile de travailler avec les composants ou de les déboguer. Bien que ces problèmes puissent être résolus avec des composants d'ordre supérieur et des props de rendu , ils vous obligent à modifier un peu votre code. Cela pourrait prêter à confusion dans une application complexe.

Les hooks sont faciles à partager, vous n'avez pas besoin de modifier vos composants avant de réutiliser la logique.

Un bon exemple en est l'utilisation du composant d'ordre supérieur (HOC) Redux connect pour s'abonner au magasin Redux. Comme tous les HOC, pour utiliser le connect HOC, vous devez exporter le composant avec les fonctions d'ordre supérieur définies. Dans le cas de connect , nous aurons quelque chose de cette forme.

 export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

mapStateToProps et mapDispatchToProps sont des fonctions à définir.

Alors qu'à l'ère des crochets, on peut facilement obtenir le même résultat de manière nette et succincte en utilisant les crochets Redux useSelector et useDispatch .

Composants énormes

Les composants de classe contiennent généralement des effets secondaires et une logique avec état. Au fur et à mesure que l'application gagne en complexité, il est courant que le composant devienne désordonné et déroutant. En effet, les effets secondaires devraient être organisés par des méthodes de cycle de vie plutôt que par des fonctionnalités. Bien qu'il soit possible de scinder les composants et de les simplifier, cela introduit souvent un niveau d'abstraction plus élevé.

Les crochets organisent les effets secondaires par fonctionnalité et il est possible de diviser un composant en morceaux en fonction de la fonctionnalité.

Classes déroutantes

Les classes sont généralement un concept plus difficile que les fonctions. Les composants basés sur les classes React sont verbeux et un peu difficiles pour les débutants. Si vous débutez avec Javascript, vous pourriez trouver des fonctions plus faciles à utiliser en raison de leur syntaxe légère par rapport aux classes. La syntaxe peut prêter à confusion ; parfois, il est possible d'oublier de lier un gestionnaire d'événements qui pourrait casser le code.

React résout ce problème avec des composants fonctionnels et des crochets, permettant aux développeurs de se concentrer sur le projet plutôt que sur la syntaxe du code.

Par exemple, les deux composants React suivants donneront exactement le même résultat.

 import React, { Component } from "react"; export default class App extends Component { constructor(props) { super(props); this.state = { num: 0 }; this.incrementNumber = this.incrementNumber.bind(this); } incrementNumber() { this.setState({ num: this.state.num + 1 }); } render() { return ( <div> <h1>{this.state.num}</h1> <button onClick={this.incrementNumber}>Increment</button> </div> ); } }
 import React, { useState } from "react"; export default function App() { const [num, setNum] = useState(0); function incrementNumber() { setNum(num + 1); } return ( <div> <h1>{num}</h1> <button onClick={incrementNumber}>Increment</button> </div> ); }

Le premier exemple est un composant basé sur une classe tandis que le second est un composant fonctionnel. Bien qu'il s'agisse d'un exemple simple, remarquez à quel point le premier exemple est faux par rapport au second.

La convention et les règles des crochets

Avant de se plonger dans les différents crochets, il peut être utile de jeter un œil à la convention et aux règles qui s'y appliquent. Voici quelques-unes des règles qui s'appliquent aux crochets.

  1. La convention de nommage des hooks doit commencer par le préfixe use . Ainsi, nous pouvons avoir useState , useEffect , etc. Si vous utilisez des éditeurs de code modernes comme Atom et VSCode, le plugin ESLint pourrait être une fonctionnalité très utile pour les crochets React. Le plugin fournit des avertissements et des conseils utiles sur les meilleures pratiques.
  2. Les hooks doivent être appelés au niveau supérieur d'un composant, avant l'instruction return. Ils ne peuvent pas être appelés dans une instruction conditionnelle, une boucle ou des fonctions imbriquées.
  3. Les crochets doivent être appelés à partir d'une fonction React (à l'intérieur d'un composant React ou d'un autre crochet). Il ne doit pas être appelé à partir d'une fonction Vanilla JS.

Le useState

Le crochet useState est le crochet React le plus basique et le plus utile. Comme les autres crochets intégrés, ce crochet doit être importé de react pour être utilisé dans notre application.

 import {useState} from 'react'

Pour initialiser l'état, nous devons déclarer à la fois l'état et sa fonction de mise à jour et transmettre une valeur initiale.

 const [state, updaterFn] = useState('')

Nous sommes libres d'appeler notre état et notre fonction de mise à jour comme nous le voulons mais par convention, le premier élément du tableau sera notre état tandis que le deuxième élément sera la fonction de mise à jour. Il est courant de préfixer notre fonction de mise à jour avec le jeu de préfixes suivi du nom de notre état sous la forme d'une casse camel.

Par exemple, définissons un état pour contenir les valeurs de comptage.

 const [count, setCount] = useState(0)

Notez que la valeur initiale de notre état de count est définie sur 0 et non sur une chaîne vide. En d'autres termes, nous pouvons initialiser notre état avec n'importe quel type de variables JavaScript, à savoir nombre, chaîne, booléen, tableau, objet et même BigInt. Il existe une nette différence entre la définition des états avec le crochet useState et les états des composants basés sur les classes. Il est à noter que le hook useState renvoie un tableau, également connu sous le nom de variables d'état et dans l'exemple ci-dessus, nous avons déstructuré le tableau en state et la fonction de updater à jour.

Composants de rendu

La définition d'états avec le crochet useState entraîne le rendu du composant correspondant. Cependant, cela ne se produit que si React détecte une différence entre l'état précédent ou ancien et le nouvel état. React effectue la comparaison d'état à l'aide de l'algorithme Javascript Object.is .

Définition des états avec useState

Notre état de count peut être défini sur de nouvelles valeurs d'état en transmettant simplement la nouvelle valeur à la fonction de mise à jour setCount comme suit setCount(newValue) .

Cette méthode fonctionne lorsque nous ne voulons pas faire référence à la valeur d'état précédente. Si nous souhaitons faire cela, nous devons passer une fonction à la fonction setCount .

En supposant que nous voulions ajouter 5 à notre variable de count chaque fois qu'un bouton est cliqué, nous pourrions faire ce qui suit.

 import {useState} from 'react' const CountExample = () => { // initialize our count state const [count, setCount] = useState(0) // add 5 to to the count previous state const handleClick = () =>{ setCount(prevCount => prevCount + 5) } return( <div> <h1>{count} </h1> <button onClick={handleClick}>Add Five</button> </div> ) } export default CountExample

Dans le code ci-dessus, nous avons d'abord importé le hook useState de react , puis initialisé l'état count avec une valeur par défaut de 0. Nous avons créé un gestionnaire onClick pour incrémenter la valeur de count de 5 chaque fois que le bouton est cliqué. Ensuite, nous avons affiché le résultat dans une balise h1 .

Définition des tableaux et des états d'objet

Les états des tableaux et des objets peuvent être définis de la même manière que les autres types de données. Cependant, si nous souhaitons conserver des valeurs déjà existantes, nous devons utiliser l'opérateur de propagation ES6 lors de la définition des états.

L'opérateur de propagation en Javascript est utilisé pour créer un nouvel objet à partir d'un objet déjà existant. Ceci est utile ici car React compare les états avec l'opération Object.is , puis restitue en conséquence.

Considérons le code ci-dessous pour définir les états lors du clic sur un bouton.

 import {useState} from 'react' const StateExample = () => { //initialize our array and object states const [arr, setArr] = useState([2, 4]) const [obj, setObj] = useState({num: 1, name: 'Desmond'}) // set arr to the new array values const handleArrClick = () =>{ const newArr = [1, 5, 7] setArr([...arr, ...newArr]) } // set obj to the new object values const handleObjClick = () =>{ const newObj = {name: 'Ifeanyi', age: 25} setObj({...obj, ...newObj}) } return( <div> <button onClick ={handleArrClick}>Set Array State</button> <button onClick ={handleObjClick}>Set Object State</button> </div> ) } export default StateExample

Dans le code ci-dessus, nous avons créé deux états arr et obj , et les avons initialisés respectivement avec des valeurs de tableau et d'objet. Nous avons ensuite créé des gestionnaires onClick appelés handleArrClick et handleObjClick pour définir respectivement les états du tableau et de l'objet. Lorsque handleArrClick se déclenche, nous appelons setArr et utilisons l'opérateur de propagation ES6 pour diffuser des valeurs de tableau déjà existantes et y ajouter newArr .

Nous avons fait la même chose pour le gestionnaire handleObjClick . Ici, nous avons appelé setObj , réparti les valeurs d'objet existantes à l'aide de l'opérateur de propagation ES6 et mis à jour les valeurs de name et age .

Nature asynchrone de l'état d' useState

Comme nous l'avons déjà vu, nous définissons des états avec useState en passant une nouvelle valeur à la fonction updater. Si le programme de mise à jour est appelé plusieurs fois, les nouvelles valeurs seront ajoutées à une file d'attente et un nouveau rendu est effectué en conséquence à l'aide de la comparaison JavaScript Object.is .

Les états sont mis à jour de manière asynchrone. Cela signifie que le nouvel état est d'abord ajouté à un état en attente et ensuite, l'état est mis à jour. Ainsi, vous pouvez toujours obtenir l'ancienne valeur d'état si vous accédez à l'état immédiatement après sa définition.

Considérons l'exemple suivant pour observer ce comportement.

Dans le code ci-dessus, nous avons créé un état de count à l'aide du crochet useState . Nous avons ensuite créé un gestionnaire onClick pour incrémenter l'état de count chaque fois que le bouton est cliqué. Notez que bien que l'état du count ait augmenté, comme indiqué dans la balise h2 , l'état précédent est toujours enregistré dans la console. Cela est dû à la nature asynchrone du crochet.

Si nous souhaitons obtenir le nouvel état, nous pouvons le gérer de la même manière que nous gérerions les fonctions asynchrones. Voici une façon de le faire.

Ici, nous avons stocké newCountValue créé pour stocker la valeur de comptage mise à jour, puis défini l'état de count avec la valeur mise à jour. Ensuite, nous avons enregistré la valeur de comptage mise à jour dans la console.

Le crochet useEffect

useEffect est un autre crochet React important utilisé dans la plupart des projets. Il fait une chose similaire aux méthodes de cycle de vie componentDidMount , componentWillUnmount et componentDidUpdate du composant basé sur la classe. useEffect nous offre la possibilité d'écrire des codes impératifs qui peuvent avoir des effets secondaires sur l'application. Des exemples de tels effets incluent la journalisation, les abonnements, les mutations, etc.

L'utilisateur peut décider quand useEffect s'exécutera, cependant, s'il n'est pas défini, les effets secondaires s'exécuteront à chaque rendu ou re-rendu.

Prenons l'exemple ci-dessous.

 import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ console.log(count) }) return( <div> ... </div> ) }

Dans le code ci-dessus, nous avons simplement connecté count dans le useEffect . Cela s'exécutera après chaque rendu du composant.

Parfois, nous pouvons vouloir exécuter le hook une fois (sur le support) dans notre composant. Nous pouvons y parvenir en fournissant un deuxième paramètre au crochet useEffect .

 import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ setCount(count + 1) }, []) return( <div> <h1>{count}</h1> ... </div> ) }

Le crochet useEffect a deux paramètres, le premier paramètre est la fonction que nous voulons exécuter tandis que le second paramètre est un tableau de dépendances. Si le deuxième paramètre n'est pas fourni, le crochet s'exécutera en continu.

En passant un crochet vide au deuxième paramètre du crochet, nous demandons à React de n'exécuter le crochet useEffect qu'une seule fois, sur la monture. Cela affichera la valeur 1 dans la balise h1 car le décompte sera mis à jour une fois, de 0 à 1, lors du montage du composant.

Nous pourrions également exécuter notre effet secondaire chaque fois que certaines valeurs dépendantes changent. Cela peut être fait en passant ces valeurs dans la liste des dépendances.

Par exemple, nous pourrions faire en sorte que useEffect s'exécute chaque fois que le count change comme suit.

 import { useState, useEffect } from "react"; const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.log(count); }, [count]); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default App;

L' useEffect ci-dessus s'exécutera lorsque l'une de ces deux conditions sera remplie.

  1. Au montage — après le rendu du composant.
  2. Lorsque la valeur de count change.

Lors du montage, l'expression console.log s'exécutera et consignera le count à 0. Une fois le count mis à jour, la deuxième condition est remplie, donc useEffect s'exécute à nouveau, cela continuera chaque fois que le bouton est cliqué.

Une fois que nous avons fourni le deuxième argument à useEffect , nous nous attendons à lui transmettre toutes les dépendances. Si vous avez installé ESLINT , il affichera une erreur de charpie si une dépendance n'est pas transmise à la liste des paramètres. Cela pourrait également faire en sorte que l'effet secondaire se comporte de manière inattendue, surtout s'il dépend des paramètres qui ne sont pas transmis.

Nettoyer l'effet

useEffect nous permet également de nettoyer les ressources avant le démontage du composant. Cela peut être nécessaire pour éviter les fuites de mémoire et rendre l'application plus efficace. Pour ce faire, nous retournerions la fonction de nettoyage à la fin du crochet.

 useEffect(() => { console.log('mounted') return () => console.log('unmounting... clean up here') })

Le crochet useEffect ci-dessus sera connecté lorsque le composant sera mounted . Le démontage… le nettoyage ici sera enregistré lorsque le composant sera démonté. Cela peut se produire lorsque le composant est supprimé de l'interface utilisateur.

Le processus de nettoyage suit généralement le formulaire ci-dessous.

 useEffect(() => { //The effect we intend to make effect //We then return the clean up return () => the cleanup/unsubscription })

Bien que vous ne trouviez peut-être pas autant de cas d'utilisation pour les abonnements useEffect , cela est utile lorsqu'il s'agit d'abonnements et de minuteries. En particulier, lorsqu'il s'agit de sockets Web, vous devrez peut-être vous désabonner du réseau pour économiser des ressources et améliorer les performances lorsque le composant se démonte.

Récupération et récupération de données avec useEffect

L'un des cas d'utilisation les plus courants du crochet useEffect est la récupération et la prélecture de données à partir d'une API.

Pour illustrer cela, nous utiliserons de fausses données utilisateur que j'ai créées à partir de JSONPlaceholder pour récupérer des données avec le crochet useEffect .

 import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [users, setUsers] = useState([]); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/users"; useEffect(() => { const fetchUsers = async () => { const { data } = await axios.get(endPoint); setUsers(data); }; fetchUsers(); }, []); return ( <div className="App"> {users.map((user) => ( <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> ))} </div> ); }

Dans le code ci-dessus, nous avons créé un état des users à l'aide du crochet useState . Ensuite, nous avons récupéré les données d'une API à l'aide d'Axios. Il s'agit d'un processus asynchrone, et nous avons donc utilisé la fonction async/wait, nous aurions pu également utiliser le point puis la syntaxe. Puisque nous avons récupéré une liste d'utilisateurs, nous l'avons simplement cartographiée pour afficher les données.

Notez que nous avons passé un paramètre vide au crochet. Cela garantit qu'il n'est appelé qu'une seule fois lorsque le composant est monté.

Nous pouvons également récupérer les données lorsque certaines conditions changent. Nous allons le montrer dans le code ci-dessous.

 import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [userIDs, setUserIDs] = useState([]); const [user, setUser] = useState({}); const [currentID, setCurrentID] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/userdata/users"; useEffect(() => { axios.get(endPoint).then(({ data }) => setUserIDs(data)); }, []); useEffect(() => { const fetchUserIDs = async () => { const { data } = await axios.get(`${endPoint}/${currentID}`}); setUser(data); }; fetchUserIDs(); }, [currentID]); const moveToNextUser = () => { setCurrentID((prevId) => (prevId < userIDs.length ? prevId + 1 : prevId)); }; const moveToPrevUser = () => { setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1)); }; return ( <div className="App"> <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> <button onClick={moveToPrevUser}>Prev</button> <button onClick={moveToNextUser}>Next</button> </div> ); }

Ici, nous avons créé deux crochets useEffect . Dans le premier, nous avons utilisé la syntaxe point puis pour obtenir tous les utilisateurs de notre API. Ceci est nécessaire pour déterminer le nombre d'utilisateurs.

Nous avons ensuite créé un autre crochet useEffect pour obtenir un utilisateur basé sur l' id . Cet useEffect récupère les données chaque fois que l'identifiant change. Pour garantir cela, nous avons passé l' id dans la liste des dépendances.

Ensuite, nous avons créé des fonctions pour mettre à jour la valeur de notre id chaque fois que les boutons sont cliqués. Une fois que la valeur de l' id a changé, useEffect s'exécute à nouveau et récupère les données.

Si nous le voulons, nous pouvons même nettoyer ou annuler le jeton basé sur la promesse dans Axios, nous pourrions le faire avec la méthode de nettoyage décrite ci-dessus.

 useEffect(() => { const source = axios.CancelToken.source(); const fetchUsers = async () => { const { data } = await axios.get(`${endPoint}/${num}`, { cancelToken: source.token }); setUser(data); }; fetchUsers(); return () => source.cancel(); }, [num]);

Ici, nous avons passé le jeton Axios comme deuxième paramètre à axios.get . Lorsque le composant se démonte, nous annulons alors l'abonnement en appelant la méthode d'annulation de l'objet source.

Le crochet useReducer

Le crochet useReducer est un crochet React très utile qui fait une chose similaire au crochet useState . Selon la documentation de React, ce crochet doit être utilisé pour gérer une logique plus complexe que le crochet useState . Il convient de noter que le crochet useState est implémenté en interne avec le crochet useReducer.

Le hook prend un réducteur comme argument et peut éventuellement prendre l'état initial et une fonction init comme arguments.

 const [state, dispatch] = useReducer(reducer, initialState, init)

Ici, init est une fonction et elle est utilisée chaque fois que nous voulons créer l'état initial paresseusement.

Voyons comment implémenter le hook useReducer en créant une simple application de tâches, comme indiqué dans le bac à sable ci-dessous.

Exemple de tâche

Tout d'abord, nous devons créer notre réducteur pour contenir les états.

 export const ADD_TODO = "ADD_TODO"; export const REMOVE_TODO = "REMOVE_TODO"; export const COMPLETE_TODO = "COMPLETE_TODO"; const reducer = (state, action) => { switch (action.type) { case ADD_TODO: const newTodo = { id: action.id, text: action.text, completed: false }; return [...state, newTodo]; case REMOVE_TODO: return state.filter((todo) => todo.id !== action.id); case COMPLETE_TODO: const completeTodo = state.map((todo) => { if (todo.id === action.id) { return { ...todo, completed: !todo.completed }; } else { return todo; } }); return completeTodo; default: return state; } }; export default reducer;

Nous avons créé trois constantes correspondant à nos types d'action. Nous aurions pu utiliser directement les chaînes mais cette méthode est préférable pour éviter les fautes de frappe.

Ensuite, nous avons créé notre fonction de réduction. Comme dans Redux , le reducer doit prendre l'état et l'objet action. Mais contrairement à Redux, nous n'avons pas besoin d'initialiser notre réducteur ici.

De plus, pour de nombreux cas d'utilisation de la gestion d'état, un useReducer ainsi que l' dispatch exposé via le contexte peuvent permettre à une application plus large de déclencher des actions, de mettre à jour state et de l'écouter.

Ensuite, nous avons utilisé les instructions switch pour vérifier le type d'action passé par l'utilisateur. Si le type d'action est ADD_TODO , on veut passer une nouvelle to-do et si c'est REMOVE_TODO , on veut filtrer les to-dos et supprimer celle qui correspond à l' id passé par l'utilisateur. S'il s'agit de COMPLETE_TODO , nous voulons mapper les tâches et basculer celle avec l' id transmis par l'utilisateur.

Voici le fichier App.js où nous avons implémenté le reducer .

 import { useReducer, useState } from "react"; import "./styles.css"; import reducer, { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } from "./reducer"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = [ { id: id, text: "First Item", completed: false } ]; //We could also pass an empty array as the initial state //const initialState = [] const [state, dispatch] = useReducer(reducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); dispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="App"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit">+</button> </form> <div className="todos"> {state.map((todo) => ( <div key={todo.id} className="todoItem"> <p className={todo.completed && "strikethrough"}>{todo.text}</p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ))} </div> </div> ); }

Ici, nous avons créé un formulaire contenant un élément d'entrée, pour recueillir l'entrée de l'utilisateur, et un bouton pour déclencher l'action. Lorsque le formulaire est soumis, nous avons envoyé une action de type ADD_TODO , en passant un nouvel identifiant et un nouveau texte de tâche. Nous avons créé un nouvel identifiant en incrémentant la valeur de l'identifiant précédent de 1. Nous avons ensuite effacé la zone de texte de saisie. Pour supprimer et terminer la tâche, nous avons simplement envoyé les actions appropriées. Celles-ci ont déjà été implémentées dans le réducteur comme indiqué ci-dessus.

Cependant, la magie opère parce que nous utilisons le crochet useReducer . Ce crochet accepte le réducteur et l'état initial et renvoie l'état et la fonction de répartition. Ici, la fonction dispatch a le même objectif que la fonction setter pour le hook useState et nous pouvons l'appeler comme nous voulons au lieu de dispatch .

Pour afficher les éléments à faire, nous avons simplement cartographié la liste des tâches renvoyées dans notre objet d'état, comme indiqué dans le code ci-dessus.

Cela montre la puissance du crochet useReducer . Nous pourrions également réaliser cette fonctionnalité avec le crochet useState mais comme vous pouvez le voir dans l'exemple ci-dessus, le crochet useReducer nous a aidés à garder les choses plus propres. useReducer est souvent bénéfique lorsque l'objet d'état est une structure complexe et est mis à jour de différentes manières par rapport à un simple remplacement de valeur. De plus, une fois que ces fonctions de mise à jour deviennent plus compliquées, useReducer permet de conserver facilement toute cette complexité dans une fonction de réduction (qui est une fonction JS pure), ce qui facilite l'écriture de tests pour la fonction de réduction seule.

Nous aurions également pu passer le troisième argument au crochet useReducer pour créer l'état initial paresseusement. Cela signifie que nous pourrions calculer l'état initial dans une fonction init .

Par exemple, nous pourrions créer une fonction init comme suit :

 const initFunc = () => [ { id: id, text: "First Item", completed: false } ]

puis passez-le à notre crochet useReducer .

 const [state, dispatch] = useReducer(reducer, initialState, initFunc)

Si nous faisons cela, initFunc remplacera le initialState que nous avons fourni et l'état initial sera calculé paresseusement.

Le crochet useContext

L'API React Context fournit un moyen de partager des états ou des données dans l'arborescence des composants React. L'API est disponible dans React, en tant que fonctionnalité expérimentale, depuis un certain temps, mais son utilisation est devenue sûre dans React 16.3.0. L'API facilite le partage de données entre les composants tout en éliminant le forage d'accessoires.

Bien que vous puissiez appliquer le React Context à l'ensemble de votre application, il est également possible de l'appliquer à une partie de l'application.

Pour utiliser le crochet, vous devez d'abord créer un contexte à l'aide React.createContext et ce contexte peut ensuite être transmis au crochet.

Pour démontrer l'utilisation du crochet useContext , créons une application simple qui augmentera la taille de la police dans toute notre application.

Créons notre contexte dans le fichier context.js .

 import { createContext } from "react"; //Here, we set the initial fontSize as 16. const fontSizeContext = createContext(16); export default fontSizeContext;

Ici, nous avons créé un contexte et lui avons passé une valeur initiale de 16 , puis nous avons exporté le contexte. Ensuite, connectons notre contexte à notre application.

 import FontSizeContext from "./context"; import { useState } from "react"; import PageOne from "./PageOne"; import PageTwo from "./PageTwo"; const App = () => { const [size, setSize] = useState(16); return ( <FontSizeContext.Provider value={size}> <PageOne /> <PageTwo /> <button onClick={() => setSize(size + 5)}>Increase font</button> <button onClick={() => setSize((prevSize) => Math.min(11, prevSize - 5)) } > Decrease font </button> </FontSizeContext.Provider> ); }; export default App;

Dans le code ci-dessus, nous avons enveloppé l'ensemble de notre arborescence de composants avec FontSizeContext.Provider et avons transmis la size à sa valeur prop. Ici, la size est un état créé avec le crochet useState . Cela nous permet de changer la valeur prop chaque fois que l'état de la size change. En enveloppant l'intégralité du composant avec le Provider , nous pouvons accéder au contexte n'importe où dans notre application.

Par exemple, nous avons accédé au contexte dans <PageOne /> et <PageTwo /> . En conséquence, la taille de la police augmentera sur ces deux composants lorsque nous l'augmentons à partir du fichier App.js Nous pouvons augmenter ou diminuer la taille de la police à partir des boutons comme indiqué ci-dessus et une fois que nous le faisons, la taille de la police change dans toute l'application.

 import { useContext } from "react"; import context from "./context"; const PageOne = () => { const size = useContext(context); return <p style={{ fontSize: `${size}px` }}>Content from the first page</p>; }; export default PageOne;

Ici, nous avons accédé au contexte en utilisant le crochet useContext de notre composant PageOne . Nous avons ensuite utilisé ce contexte pour définir notre propriété font-size. Une procédure similaire s'applique au fichier PageTwo.js .

Les thèmes ou d'autres configurations au niveau de l'application d'ordre supérieur sont de bons candidats pour les contextes.

Utiliser useContext et useReducer

Lorsqu'il est utilisé avec le crochet useReducer , useContext nous permet de créer notre propre système de gestion d'état. Nous pouvons créer des états globaux et les gérer facilement dans notre application.

Améliorons notre application de tâches à l'aide de l'API de contexte.

Comme d'habitude, nous devons créer un todoContext dans le fichier todoContext.js .

 import { createContext } from "react"; const initialState = []; export default createContext(initialState);

Ici, nous avons créé le contexte, en passant une valeur initiale d'un tableau vide. Ensuite, nous avons exporté le contexte.

Refactorisons notre fichier App.js en séparant la liste de tâches et les éléments.

 import { useReducer, useState } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import TodoList from "./TodoList"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <div className="app"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </TodoContext.Provider> ); }

Ici, nous avons enveloppé notre fichier App.js avec le TodoContext.Provider puis nous lui avons passé les valeurs de retour de notre todoReducer . Cela rend l'état et la fonction de dispatch du réducteur accessibles dans toute notre application.

Nous avons ensuite séparé l'affichage des tâches en un composant TodoList . Nous l'avons fait sans forage d'hélice, grâce à l'API Context. Jetons un coup d'œil au fichier TodoList.js .

 import React, { useContext } from "react"; import TodoContext from "./todoContext"; import Todo from "./Todo"; const TodoList = () => { const [state] = useContext(TodoContext); return ( <div className="todos"> {state.map((todo) => ( <Todo key={todo.id} todo={todo} /> ))} </div> ); }; export default TodoList;

En utilisant la déstructuration de tableau, nous pouvons accéder à l'état (en quittant la fonction de répartition) à partir du contexte à l'aide du crochet useContext . Nous pouvons ensuite cartographier l'état et afficher les éléments à faire. Nous avons toujours extrait cela dans un composant Todo . La fonction de carte ES6 + nous oblige à transmettre une clé unique et puisque nous avons besoin d'une tâche spécifique, nous la transmettons également.

Jetons un coup d'œil au composant Todo .

 import React, { useContext } from "react"; import TodoContext from "./todoContext"; import { REMOVE_TODO, COMPLETE_TODO } from "./todoReducer"; const Todo = ({ todo }) => { const [, dispatch] = useContext(TodoContext); const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="todoItem"> <p className={todo.completed ? "strikethrough" : "nostrikes"}> {todo.text} </p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ); }; export default Todo;

Toujours en utilisant la déstructuration de tableau, nous avons accédé à la fonction dispatch depuis le contexte. Cela nous permet de définir les fonctions completeTodo et removeTodo comme déjà discuté dans la section useReducer . Avec le prop todo passé de todoList.js nous pouvons afficher un élément de tâche. Nous pouvons également le marquer comme terminé et supprimer la tâche à faire comme bon nous semble.

Il est également possible d'imbriquer plus d'un fournisseur de contexte à la racine de notre application. Cela signifie que nous pouvons utiliser plusieurs contextes pour exécuter différentes fonctions dans une application.

Pour le démontrer, ajoutons un thème à l'exemple de la tâche.

Voici ce que nous allons construire.

Encore une fois, nous devons créer themeContext . Pour ce faire, créez un fichier themeContext.js et ajoutez les codes suivants.

 import { createContext } from "react"; import colors from "./colors"; export default createContext(colors.light);

Ici, nous avons créé un contexte et passé colors.light comme valeur initiale. Définissons les couleurs avec cette propriété dans le fichier colors.js .

 const colors = { light: { backgroundColor: "#fff", color: "#000" }, dark: { backgroundColor: "#000", color: "#fff" } }; export default colors;

Dans le code ci-dessus, nous avons créé un objet colors contenant des propriétés claires et sombres. Chaque propriété a backgroundColor et un objet color .

Ensuite, nous créons le themeReducer pour gérer les états du thème.

 import Colors from "./colors"; export const LIGHT = "LIGHT"; export const DARK = "DARK"; const themeReducer = (state, action) => { switch (action.type) { case LIGHT: return { ...Colors.light }; case DARK: return { ...Colors.dark }; default: return state; } }; export default themeReducer;

Comme tous les reducers, le themeReducer prend l'état et l'action. Il utilise ensuite l'instruction switch pour déterminer l'action en cours. S'il est de type LIGHT , nous assignons simplement les props Colors.light et s'il est de type DARK , nous affichons les props Colors.dark . Nous aurions pu facilement le faire avec le crochet useState , mais nous choisissons useReducer pour ramener le point à la maison.

Après avoir configuré le themeReducer , nous pouvons ensuite l'intégrer dans notre fichier App.js

 import { useReducer, useState, useCallback } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import ThemeContext from "./themeContext"; import TodoList from "./TodoList"; import themeReducer, { DARK, LIGHT } from "./themeReducer"; import Colors from "./colors"; import ThemeToggler from "./ThemeToggler"; const themeSetter = useCallback( theme => themeDispatch({type: theme}, [themeDispatch]); export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const [themeState, themeDispatch] = useReducer(themeReducer, Colors.light); const themeSetter = useCallback( (theme) => { themeDispatch({ type: theme }); }, [themeDispatch] ); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <ThemeContext.Provider value={[ themeState, themeSetter ]} > <div className="app" style={{ ...themeState }}> <ThemeToggler /> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </ThemeContext.Provider> </TodoContext.Provider> ); }

Dans le code ci-dessus, nous avons ajouté quelques éléments à notre application de tâches déjà existante. Nous avons commencé par importer le ThemeContext , themeReducer , ThemeToggler et Colors . We created a reducer using the useReducer hook, passing the themeReducer and an initial value of Colors.light to it. This returned the themeState and themeDispatch to us.

We then nested our component with the provider function from the ThemeContext , passing the themeState and the dispatch functions to it. We also added theme styles to it by spreading out the themeStates . This works because the colors object already defined properties similar to what the JSX styles will accept.

However, the actual theme toggling happens in the ThemeToggler component. Jetons un coup d'œil.

 import ThemeContext from "./themeContext"; import { useContext, useState } from "react"; import { DARK, LIGHT } from "./themeReducer"; const ThemeToggler = () => { const [showLight, setShowLight] = useState(true); const [themeState, themeSetter] = useContext(ThemeContext); const dispatchDarkTheme = () => themeSetter(DARK); const dispatchLightTheme = () => themeSetter(LIGHT); const toggleTheme = () => { showLight ? dispatchDarkTheme() : dispatchLightTheme(); setShowLight(!showLight); }; console.log(themeState); return ( <div> <button onClick={toggleTheme}> {showLight ? "Change to Dark Theme" : "Change to Light Theme"} </button> </div> ); }; export default ThemeToggler;

In this component, we used the useContext hook to retrieve the values we passed to the ThemeContext.Provider from our App.js file. As shown above, these values include the ThemeState , dispatch function for the light theme, and dispatch function for the dark theme. Thereafter, we simply called the dispatch functions to toggle the themes. We also created a state showLight to determine the current theme. This allows us to easily change the button text depending on the current theme.

The useMemo Hook

The useMemo hook is designed to memoize expensive computations. Memoization simply means caching. It caches the computation result with respect to the dependency values so that when the same values are passed, useMemo will just spit out the already computed value without recomputing it again. This can significantly improve performance when done correctly.

The hook can be used as follows:

 const memoizedResult = useMemo(() => expensiveComputation(a, b), [a, b])

Let's consider three cases of the useMemo hook.

  1. When the dependency values, a and b remain the same.
    The useMemo hook will return the already computed memoized value without recomputation.
  2. When the dependency values, a and b change.
    The hook will recompute the value.
  3. When no dependency value is passed.
    The hook will recompute the value.

Let's take a look at an example to demonstrate this concept.

In the example below, we'll be computing the PAYE and Income after PAYE of a company's employees with fake data from JSONPlaceholder.

The calculation will be based on the personal income tax calculation procedure for Nigeria providers by PricewaterhouseCoopers available here.

This is shown in the sandbox below.

First, we queried the API to get the employees' data. We also get data for each employee (with respect to their employee id).

const [employee, setEmployee] = useState({}); const [employees, setEmployees] = useState([]); const [num, setNum] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; useEffect(() => { const getEmployee = async () => { const { data } = await axios.get(`${endPoint}/${num}`); setEmployee(data); }; getEmployee(); }, [num]); useEffect(() => { axios.get(endPoint).then(({ data }) => setEmployees(data)); }, [num]);

Nous avons utilisé axios et la méthode async/await wait dans le premier useEffect puis le point puis la syntaxe dans le second. Ces deux approches fonctionnent de la même manière.

Ensuite, en utilisant les données sur les employés que nous avons obtenues ci-dessus, calculons les variables de soulagement :

 const taxVariablesCompute = useMemo(() => { const { income, noOfChildren, noOfDependentRelatives } = employee; //supposedly complex calculation //tax relief computations for relief Allowance, children relief, // relatives relief and pension relief const reliefs = reliefAllowance1 + reliefAllowance2 + childrenRelief + relativesRelief + pensionRelief; return reliefs; }, [employee]);

C'est un calcul assez complexe et nous avons donc dû l'envelopper dans un hook useMemo pour le mémoriser ou l'optimiser. Le mémoriser de cette manière garantira que le calcul ne sera pas recalculé si nous essayons d'accéder à nouveau au même employé.

De plus, en utilisant les valeurs d'allègement fiscal obtenues ci-dessus, nous aimerions calculer le PAYE et le revenu après PAYE.

 const taxCalculation = useMemo(() => { const { income } = employee; let taxableIncome = income - taxVariablesCompute; let PAYE = 0; //supposedly complex calculation //computation to compute the PAYE based on the taxable income and tax endpoints const netIncome = income - PAYE; return { PAYE, netIncome }; }, [employee, taxVariablesCompute]);

Nous avons effectué le calcul de la taxe (un calcul assez complexe) en utilisant les variables fiscales calculées ci-dessus, puis nous l'avons mémorisé avec le crochet useMemo .

Le code complet est disponible ici.

Cela suit la procédure de calcul de la taxe indiquée ici. Nous avons d'abord calculé l'allégement fiscal en tenant compte du revenu, du nombre d'enfants et du nombre de parents à charge. Ensuite, nous avons multiplié le revenu imposable par les taux de l'IPP par étapes. Bien que le calcul en question ne soit pas entièrement nécessaire pour ce didacticiel, il est fourni pour nous montrer pourquoi useMemo peut être nécessaire. C'est aussi un calcul assez complexe et nous devrons peut-être le mémoriser avec useMemo comme indiqué ci-dessus.

Après avoir calculé les valeurs, nous avons simplement affiché le résultat.

Notez ce qui suit à propos du crochet useMemo .

  • useMemo ne doit être utilisé que lorsqu'il est nécessaire d'optimiser le calcul. Autrement dit, lorsque le recalcul coûte cher.
  • Il est conseillé d'écrire d'abord le calcul sans mémorisation et de ne le mémoriser que s'il cause des problèmes de performances.
  • L'utilisation inutile et non pertinente du hook useMemo peut même aggraver les problèmes de performances.
  • Parfois, trop de mémorisation peut également entraîner des problèmes de performances.

Le crochet useCallback

useCallback le même objectif que useMemo mais il renvoie un rappel mémorisé au lieu d'une valeur mémorisée. En d'autres termes, useCallback revient à passer useMemo sans appel de fonction.

Par exemple, considérez les codes suivants ci-dessous.

 import React, {useCallback, useMemo} from 'react' const MemoizationExample = () => { const a = 5 const b = 7 const memoResult = useMemo(() => a + b, [a, b]) const callbackResult = useCallback(a + b, [a, b]) console.log(memoResult) console.log(callbackResult) return( <div> ... </div> ) } export default MemoizationExample

Dans l'exemple ci-dessus, memoResult et callbackResult donneront la même valeur de 12 . Ici, useCallback renverra une valeur mémorisée. Cependant, nous pourrions également lui faire renvoyer un rappel mémorisé en le passant en tant que fonction.

Le useCallback ci-dessous renverra un rappel mémorisé.

 ... const callbackResult = useCallback(() => a + b, [a, b]) ...

Nous pouvons ensuite déclencher le rappel lorsqu'une action est effectuée ou dans un crochet useEffect .

 import {useCallback, useEffect} from 'react' const memoizationExample = () => { const a = 5 const b = 7 const callbackResult = useCallback(() => a + b, [a, b]) useEffect(() => { const callback = callbackResult() console.log(callback) }) return ( <div> <button onClick= {() => console.log(callbackResult())}> Trigger Callback </button> </div> ) } export default memoizationExample

Dans le code ci-dessus, nous avons défini une fonction de rappel à l'aide du crochet useCallback . Nous avons ensuite appelé le rappel dans un crochet useEffect lorsque le composant est monté et également lorsqu'un bouton est cliqué.

useEffect et le clic sur le bouton donnent le même résultat.

Notez que les concepts, les choses à faire et à ne pas faire qui s'appliquent au crochet useMemo s'appliquent également au crochet useCallback . Nous pouvons recréer l'exemple useMemo avec useCallback .

Le crochet useRef

useRef renvoie un objet qui peut persister dans une application. Le crochet n'a qu'une seule propriété, current , et nous pouvons facilement lui passer un argument.

Il a le même objectif qu'un createRef utilisé dans les composants basés sur des classes. Nous pouvons créer une référence avec ce crochet comme suit :

 const newRef = useRef('')

Ici, nous avons créé une nouvelle référence appelée newRef et lui avons passé une chaîne vide.

Ce crochet est utilisé principalement à deux fins :

  1. Accéder ou manipuler le DOM, et
  2. Stockage d'états modifiables - ceci est utile lorsque nous ne voulons pas que le composant soit restitué lorsqu'une valeur change.

Manipuler le DOM

Lorsqu'il est passé à un élément DOM, l'objet ref pointe vers cet élément et peut être utilisé pour accéder à ses attributs et propriétés DOM.

Voici un exemple très simple pour illustrer ce concept.

 import React, {useRef, useEffect} from 'react' const RefExample = () => { const headingRef = useRef('') console.log(headingRef) return( <div> <h1 className='topheading' ref={headingRef}>This is a h1 element</h1> </div> ) } export default RefExample

Dans l'exemple ci-dessus, nous avons défini headingRef en utilisant le crochet useRef en passant une chaîne vide. Nous définissons ensuite la référence dans la balise h1 en passant ref = {headingRef} . En définissant cette ref, nous avons demandé à la headingRef de pointer vers notre élément h1 . Cela signifie que nous pouvons accéder aux propriétés de notre élément h1 à partir de la réf.

Pour voir cela, si nous vérifions la valeur de console.log(headingRef) , nous obtiendrons {current: HTMLHeadingElement} ou {current: h1} et nous pourrons évaluer toutes les propriétés ou attributs de l'élément. Une chose similaire s'applique à tout autre élément HTML.

Par exemple, nous pourrions mettre le texte en italique lors du montage du composant.

 useEffect(() => { headingRef.current.style.font; }, []);

Nous pouvons même changer le texte en autre chose.

 ... headingRef.current.innerHTML = "A Changed H1 Element"; ...

Nous pouvons également modifier la couleur d'arrière-plan du conteneur parent.

 ... headingRef.current.parentNode.style.backgroundColor = "red"; ...

Tout type de manipulation DOM peut être fait ici. Notez que headingRef.current peut être lu de la même manière que document.querySelector('.topheading') .

Un cas d'utilisation intéressant du crochet useRef dans la manipulation de l'élément DOM est de focaliser le curseur sur l'élément d'entrée. Parcourons-le rapidement.

 import {useRef, useEffect} from 'react' const inputRefExample = () => { const inputRef = useRef(null) useEffect(() => { inputRef.current.focus() }, []) return( <div> <input ref={inputRef} /> <button onClick = {() => inputRef.current.focus()}>Focus on Input </button> </div> ) } export default inputRefExample

Dans le code ci-dessus, nous avons créé inputRef à l'aide du crochet useRef , puis nous lui avons demandé de pointer vers l'élément d'entrée. Nous avons ensuite fait en sorte que le curseur se concentre sur la référence d'entrée lorsque le composant se charge et lorsque le bouton est cliqué à l'aide inputRef.current.focus() . Ceci est possible car focus() est un attribut des éléments d'entrée et la référence pourra donc évaluer les méthodes.

Les références créées dans un composant parent peuvent être évaluées au niveau du composant enfant en le transférant à l'aide React.forwardRef() . Jetons un coup d'œil.

Commençons par créer un autre composant NewInput.js et ajoutons-y les codes suivants.

 import { useRef, forwardRef } from "react"; const NewInput = forwardRef((props, ref) => { return <input placeholder={props.val} ref={ref} />; }); export default NewInput;

Ce composant accepte props et ref . Nous avons passé la référence à son accessoire ref et props.val à son accessoire d'espace réservé. Les composants React réguliers ne prennent pas d'attribut ref . Cet attribut est disponible uniquement lorsque nous l'enveloppons avec React.forwardRef comme indiqué ci-dessus.

Nous pouvons alors facilement l'appeler dans le composant parent.

 ... <NewInput val="Just an example" ref={inputRef} /> ...

Stockage des états mutables

Les références ne sont pas seulement utilisées pour manipuler des éléments DOM, elles peuvent également être utilisées pour stocker des valeurs modifiables sans restituer l'intégralité du composant.

L'exemple suivant détectera le nombre de fois qu'un bouton est cliqué sans restituer le composant.

 import { useRef } from "react"; export default function App() { const countRef = useRef(0); const increment = () => { countRef.current++; console.log(countRef); }; return ( <div className="App"> <button onClick={increment}>Increment </button> </div> ); }

Dans le code ci-dessus, nous avons incrémenté le countRef lorsque le bouton est cliqué, puis nous l'avons connecté à la console. Bien que la valeur soit incrémentée comme indiqué dans la console, nous ne pourrons voir aucun changement si nous essayons de l'évaluer directement dans notre composant. Il ne sera mis à jour dans le composant que lors du nouveau rendu.

Notez que tandis que useState est asynchrone, useRef est synchrone. En d'autres termes, la valeur est disponible immédiatement après sa mise à jour.

Le crochet useLayoutEffect

Comme le hook useEffect , useLayoutEffect est appelé après le montage et le rendu du composant. Ce hook se déclenche après la mutation DOM et il le fait de manière synchrone. En plus d'être appelé de manière synchrone après la mutation DOM, useLayoutEffect fait la même chose que useEffect .

useLayoutEffect ne doit être utilisé que pour effectuer une mutation DOM ou une mesure liée au DOM, sinon, vous devez utiliser le crochet useEffect . L'utilisation du crochet useEffect pour les fonctions de mutation DOM peut entraîner des problèmes de performances tels que le scintillement, mais useLayoutEffect les gère parfaitement car il s'exécute après que les mutations se sont produites.

Prenons quelques exemples pour illustrer ce concept.

  1. Nous obtiendrons la largeur et la hauteur de la fenêtre lors du redimensionnement.
 import {useState, useLayoutEffect} from 'react' const ResizeExample = () =>{ const [windowSize, setWindowSize] = useState({width: 0, height: 0}) useLayoutEffect(() => { const resizeWindow = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }) window.addEventListener('resize', resizeWindow) return () => window.removeEventListener('resize', resizeWindow) }, []) return ( <div> <p>width: {windowSize.width}</p> <p>height: {windowSize.height}</p> </div> ) } export default ResizeExample

Dans le code ci-dessus, nous avons créé un état windowSize avec les propriétés width et height. Ensuite, nous définissons l'état sur la largeur et la hauteur de la fenêtre actuelle respectivement lorsque la fenêtre est redimensionnée. Nous avons également nettoyé le code lors du démontage. Le processus de nettoyage est essentiel dans useLayoutEffect pour nettoyer la manipulation DOM et améliorer l'efficacité.

  1. Floutons un texte avec useLayoutEffect .
 import { useRef, useState, useLayoutEffect } from "react"; export default function App() { const paragraphRef = useRef(""); useLayoutEffect(() => { const { current } = paragraphRef; const blurredEffect = () => { current.style.color = "transparent"; current.style.textShadow = "0 0 5px rgba(0,0,0,0.5)"; }; current.addEventListener("click", blurredEffect); return () => current.removeEventListener("click", blurredEffect); }, []); return ( <div className="App"> <p ref={paragraphRef}>This is the text to blur</p> </div> ); }

Nous avons utilisé useRef et useLayoutEffect ensemble dans le code ci-dessus. Nous avons d'abord créé une référence, paragraphRef pour pointer vers notre paragraphe. Ensuite, nous avons créé un écouteur d'événement au clic pour surveiller le moment où le paragraphe est cliqué, puis l'estomper à l'aide des propriétés de style que nous avons définies. Enfin, nous avons nettoyé l'écouteur d'événements en utilisant removeEventListener .

Les useDispatch et useSelector

useDispatch est un crochet Redux pour répartir (déclencher) des actions dans une application. Il prend un objet d'action comme argument et invoque l'action. useDispatch est l'équivalence du crochet à mapDispatchToProps .

D'autre part, useSelector est un crochet Redux pour évaluer les états Redux. Il faut une fonction pour sélectionner le réducteur Redux exact dans le magasin, puis renvoie les états correspondants.

Une fois que notre magasin Redux est connecté à une application React via le fournisseur Redux, nous pouvons invoquer les actions avec useDispatch et accéder aux états avec useSelector . Chaque action et état Redux peut être évalué avec ces deux crochets.

Notez que ces états sont livrés avec React Redux (un package qui facilite l'évaluation du magasin Redux dans une application React). Ils ne sont pas disponibles dans la bibliothèque principale de Redux.

Ces crochets sont très simples à utiliser. Tout d'abord, nous devons déclarer la fonction dispatch puis la déclencher.

 import {useDispatch, useSelector} from 'react-redux' import {useEffect} from 'react' const myaction from '...' const ReduxHooksExample = () =>{ const dispatch = useDispatch() useEffect(() => { dispatch(myaction()); //alternatively, we can do this dispatch({type: 'MY_ACTION_TYPE'}) }, []) const mystate = useSelector(state => state.myReducerstate) return( ... ) } export default ReduxHooksExample

Dans le code ci-dessus, nous avons importé useDispatch et useSelector depuis react react-redux . Ensuite, dans un crochet useEffect , nous avons envoyé l'action. Nous pourrions définir l'action dans un autre fichier puis l'appeler ici ou nous pourrions la définir directement comme indiqué dans l'appel useEffect .

Une fois que nous avons envoyé les actions, nos états seront disponibles. Nous pouvons ensuite récupérer l'état à l'aide du hook useSelector comme indiqué. Les états peuvent être utilisés de la même manière que nous utiliserions les états du crochet useState .

Prenons un exemple pour illustrer ces deux crochets.

Pour démontrer ce concept, nous devons créer un magasin Redux, un réducteur et des actions. Pour simplifier les choses ici, nous utiliserons la bibliothèque Redux Toolkit avec notre fausse base de données de JSONPlaceholder.

Nous devons installer les packages suivants pour commencer. Exécutez les commandes bash suivantes.

 npm i redux @reduxjs/toolkit react-redux axios

Commençons par créer le employeesSlice.js pour gérer le réducteur et l'action pour l'API de nos employés.

 import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import axios from "axios"; const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; export const fetchEmployees = createAsyncThunk("employees/fetchAll", async () => { const { data } = await axios.get(endPoint); return data; }); const employeesSlice = createSlice({ name: "employees", initialState: { employees: [], loading: false, error: "" }, reducers: {}, extraReducers: { [fetchEmployees.pending]: (state, action) => { state.status = "loading"; }, [fetchEmployees.fulfilled]: (state, action) => { state.status = "success"; state.employees = action.payload; }, [fetchEmployees.rejected]: (state, action) => { state.status = "error"; state.error = action.error.message; } } }); export default employeesSlice.reducer;

Il s'agit de la configuration standard de la boîte à outils Redux. Nous avons utilisé le createAsyncThunk pour accéder au middleware Thunk afin d'effectuer des actions asynchrones. Cela nous a permis de récupérer la liste des employés depuis l'API. Nous avons ensuite créé la employeesSlice et retourné, "loading", "error", et les données des employés selon les types d'action.

La boîte à outils Redux facilite également la configuration du magasin. Voici le magasin.

 import { configureStore } from "@reduxjs/toolkit"; import { combineReducers } from "redux"; import employeesReducer from "./employeesSlice"; const reducer = combineReducers({ employees: employeesReducer }); export default configureStore({ reducer });;

Ici, nous avons utilisé combineReducers pour regrouper les réducteurs et la fonction configureStore fournie par la boîte à outils Redux pour configurer le magasin.

Continuons à l'utiliser dans notre application.

Tout d'abord, nous devons connecter Redux à notre application React. Idéalement, cela devrait être fait à la racine de notre application. J'aime le faire dans le fichier index.js .

 import React, { StrictMode } from "react"; import ReactDOM from "react-dom"; import store from "./redux/store"; import { Provider } from "react-redux"; import App from "./App"; const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <StrictMode> <App /> </StrictMode> </Provider>, rootElement );

Ici, j'ai importé le magasin que j'ai créé ci-dessus ainsi que le Provider de react-redux .

Ensuite, j'ai enveloppé l'ensemble de l'application avec la fonction Provider , en lui passant le magasin. Cela rend le magasin accessible dans toute notre application.

Nous pouvons ensuite utiliser les useDispatch et useSelector pour récupérer les données.

Faisons cela dans notre fichier App.js

 import { useDispatch, useSelector } from "react-redux"; import { fetchEmployees } from "./redux/employeesSlice"; import { useEffect } from "react"; export default function App() { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchEmployees()); }, [dispatch]); const employeesState = useSelector((state) => state.employees); const { employees, loading, error } = employeesState; return ( <div className="App"> {loading ? ( "Loading..." ) : error ? ( <div>{error}</div> ) : ( <> <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <h3>{`${employee.firstName} ${employee.lastName}`}</h3> </div> ))} </> )} </div> ); }

Dans le code ci-dessus, nous avons utilisé le hook useDispatch pour appeler l'action fetchEmployees créée dans le fichier employeesSlice.js . Cela rend l'état des employés disponible dans notre application. Ensuite, nous avons utilisé le crochet useSelector pour obtenir les états. Par la suite, nous avons affiché les résultats en cartographiant à travers les employees .

Le crochet useHistory

La navigation est très importante dans une application React. Bien que vous puissiez y parvenir de plusieurs manières, React Router fournit un moyen simple, efficace et populaire d'obtenir un routage dynamique dans une application React. De plus, React Router fournit quelques crochets pour évaluer l'état du routeur et effectuer la navigation sur le navigateur, mais pour les utiliser, vous devez d'abord configurer correctement votre application.

Pour utiliser n'importe quel crochet React Router, nous devons d'abord envelopper notre application avec BrowserRouter . Nous pouvons ensuite imbriquer les routes avec Switch et Route .

Mais d'abord, nous devons installer le package en exécutant les commandes suivantes.

 npm install react-router-dom

Ensuite, nous devons configurer notre application comme suit. J'aime faire cela dans mon fichier App.js

 import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Employees from "./components/Employees"; export default function App() { return ( <div className="App"> <Router> <Switch> <Route path='/'> <Employees /> </Route> ... </Switch> </Router> </div> ); }

Nous pourrions avoir autant de routes que possible en fonction du nombre de composants que nous souhaitons rendre. Ici, nous n'avons rendu que le composant Employees . L'attribut path indique à React Router DOM le chemin du composant et peut être évalué avec une chaîne de requête ou diverses autres méthodes.

L'ordre compte ici. La route racine doit être placée sous la route enfant et ainsi de suite. Pour remplacer cet ordre, vous devez inclure le mot-clé exact sur la route racine.

 <Route path='/' exact > <Employees /> </Route>

Maintenant que nous avons configuré le routeur, nous pouvons ensuite utiliser le crochet useHistory et d'autres crochets React Router dans notre application.

Pour utiliser le crochet useHistory , nous devons d'abord le déclarer comme suit.

 import {useHistory} from 'history' import {useHistory} from 'react-router-dom' const Employees = () =>{ const history = useHistory() ... }

Si nous enregistrons l'historique dans la console, nous verrons plusieurs propriétés qui lui sont associées. Ceux-ci incluent block , createHref , go , goBack , goForward , length , listen , location , push , replace . Bien que toutes ces propriétés soient utiles, vous utiliserez probablement history.push et history.replace plus souvent que d'autres propriétés.

Utilisons cette propriété pour passer d'une page à l'autre.

En supposant que nous voulions récupérer des données sur un employé particulier lorsque nous cliquons sur son nom. Nous pouvons utiliser le crochet useHistory pour naviguer vers la nouvelle page où les informations de l'employé seront affichées.

 function moveToPage = (id) =>{ history.push(`/employees/${id}`) }

Nous pouvons implémenter cela dans notre fichier Employee.js en ajoutant ce qui suit.

 import { useEffect } from "react"; import { Link, useHistory, useLocation } from "react-router-dom"; export default function Employees() { const history = useHistory(); function pushToPage = (id) => { history.push(`/employees/${id}`) } ... return ( <div> ... <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <span>{`${employee.firstName} ${employee.lastName} `}</span> <button onClick={pushToPage(employee.id)}> » </button> </div> ))} </div> ); }

Dans la fonction pushToPage , nous avons utilisé l' history du crochet useHistory pour accéder à la page de l'employé et lui transmettre l'identifiant de l'employé.

Le crochet useLocation

Ce crochet est également livré avec React Router DOM. C'est un crochet très populaire utilisé pour travailler avec le paramètre de chaîne de requête. Ce hook est similaire à window.location dans le navigateur.

 import {useLocation} from 'react' const LocationExample = () =>{ const location = useLocation() return ( ... ) } export default LocationExample

Le crochet useLocation renvoie le pathname d'accès, le paramètre de search , le hash et state . Les paramètres les plus couramment utilisés incluent le pathname d'accès et la search , mais vous pouvez également utiliser hash et state beaucoup de choses dans votre application.

La propriété location pathname renverra le chemin que nous avons défini dans notre configuration Route . Tandis que la search renverra le paramètre de recherche de la requête, le cas échéant. Par exemple, si nous transmettons 'http://mywebsite.com/employee/?id=1' à notre requête, le pathname d'accès serait /employee et la search serait ?id=1 .

On peut alors récupérer les différents paramètres de recherche en utilisant des packages comme query-string ou en les codant.

Le crochet useParams

Si nous configurons notre Route avec un paramètre d'URL dans son attribut de chemin, nous pouvons évaluer ces paramètres en tant que paires clé/valeur avec le crochet useParams .

Par exemple, supposons que nous ayons la Route suivante.

 <Route path='/employees/:id' > <Employees /> </Route>

La Route attendra un identifiant dynamique à la place de :id .

Avec le crochet useParams , nous pouvons évaluer l'identifiant transmis par l'utilisateur, le cas échéant.

Par exemple, en supposant que l'utilisateur passe ce qui suit en fonction avec history.push ,

 function goToPage = () => { history.push(`/employee/3`) }

Nous pouvons utiliser le crochet useParams pour accéder à ce paramètre d'URL comme suit.

 import {useParams} from 'react-router-dom' const ParamsExample = () =>{ const params = useParams() console.log(params) return( <div> ... </div> ) } export default ParamsExample

Si nous enregistrons les params dans la console, nous obtiendrons l'objet suivant {id: "3"} .

Le useRouteMatch

Ce hook permet d'accéder à l'objet match. Il renvoie la correspondance la plus proche d'un composant si aucun argument ne lui est fourni.

L'objet match renvoie plusieurs paramètres, dont le path (identique au chemin spécifié dans Route), l' URL , l'objet params et isExact .

Par exemple, nous pouvons utiliser useRouteMatch pour renvoyer des composants basés sur la route.

 import { useRouteMatch } from "react-router-dom"; import Employees from "..."; import Admin from "..." const CustomRoute = () => { const match = useRouteMatch("/employees/:id"); return match ? ( <Employee /> ) : ( <Admin /> ); }; export default CustomRoute;

Dans le code ci-dessus, nous avons défini le chemin d'un itinéraire avec useRouteMatch , puis rendu le composant <Employee /> ou <Admin /> en fonction de l'itinéraire sélectionné par l'utilisateur.

Pour que cela fonctionne, nous devons encore ajouter la route à notre fichier App.js

 ... <Route> <CustomRoute /> </Route> ...

Construire un crochet personnalisé

Selon la documentation React, la construction d'un crochet personnalisé nous permet d'extraire une logique dans une fonction réutilisable. Cependant, vous devez vous assurer que toutes les règles qui s'appliquent aux crochets React s'appliquent à votre crochet personnalisé. Vérifiez les règles du crochet React en haut de ce didacticiel et assurez-vous que votre crochet personnalisé est conforme à chacun d'eux.

Les crochets personnalisés nous permettent d'écrire des fonctions une seule fois et de les réutiliser chaque fois qu'elles sont nécessaires et obéissent donc au principe DRY.

Par exemple, nous pourrions créer un crochet personnalisé pour obtenir la position de défilement sur notre page comme suit.

 import { useLayoutEffect, useState } from "react"; export const useScrollPos = () => { const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 }); useLayoutEffect(() => { const getScrollPos = () => setScrollPos({ x: window.pageXOffset, y: window.pageYOffset }); window.addEventListener("scroll", getScrollPos); return () => window.removeEventListener("scroll", getScrollPos); }, []); return scrollPos; };

Ici, nous avons défini un crochet personnalisé pour déterminer la position de défilement sur une page. Pour y parvenir, nous avons d'abord créé un état, scrollPos , pour stocker la position de défilement. Comme cela modifiera le DOM, nous devons utiliser useLayoutEffect au lieu de useEffect . Nous avons ajouté un écouteur d'événement de défilement pour capturer les positions de défilement x et y, puis nous avons nettoyé l'écouteur d'événement. Enfin, nous sommes revenus à la position de défilement.

Nous pouvons utiliser ce crochet personnalisé n'importe où dans notre application en l'appelant et en l'utilisant comme nous utiliserions n'importe quel autre état.

 import {useScrollPos} from './Scroll' const App = () =>{ const scrollPos = useScrollPos() console.log(scrollPos.x, scrollPos.y) return ( ... ) } export default App

Ici, nous avons importé le crochet personnalisé useScrollPos que nous avons créé ci-dessus. Ensuite, nous l'avons initialisé, puis enregistré la valeur sur notre console. Si nous faisons défiler la page, le crochet nous montrera la position de défilement à chaque étape du défilement.

Nous pouvons créer des crochets personnalisés pour faire à peu près tout ce que nous pouvons imaginer dans notre application. Comme vous pouvez le voir, nous devons simplement utiliser le crochet React intégré pour exécuter certaines fonctions. Nous pouvons également utiliser des bibliothèques tierces pour créer des crochets personnalisés, mais si nous le faisons, nous devrons installer cette bibliothèque pour pouvoir utiliser le crochet.

Conclusion

Dans ce didacticiel, nous avons examiné attentivement certains crochets React utiles que vous utiliserez dans la plupart de vos applications. Nous avons examiné ce qu'ils présentent et comment les utiliser dans votre application. Nous avons également examiné plusieurs exemples de code pour vous aider à comprendre ces hooks et à les appliquer à votre application.

Je vous encourage à essayer ces crochets dans votre propre application pour mieux les comprendre.

Ressources de la documentation React

  • FAQ sur les crochets
  • Boîte à outils Redux
  • Utilisation du crochet d'état
  • Utilisation du crochet d'effet
  • Référence de l'API Hooks
  • Crochets React Redux
  • React Router Hooks