Crochets de réaction utiles que vous pouvez utiliser dans vos projets
Publié: 2022-03-10Les 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.
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)
Où 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.
- La convention de nommage des hooks doit commencer par le préfixe
use
. Ainsi, nous pouvons avoiruseState
,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. - 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.
- 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.
- Au montage — après le rendu du composant.
- 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.
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.
- When the dependency values, a and b remain the same.
TheuseMemo
hook will return the already computed memoized value without recomputation. - When the dependency values, a and b change.
The hook will recompute the value. - 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 :
- Accéder ou manipuler le DOM, et
- 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.
- 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é.
- 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