Comment utiliser une API de contexte avec des crochets tout en évitant les goulots d'étranglement
L'API de contexte est une fonctionnalité intéressante proposée par React, mais il peut être difficile de l'exécuter correctement. Apprenez à créer et à utiliser efficacement une API de contexte avec l'utilisation de React Hooks sans problèmes de performances. En commençant par une implémentation naïve, nous allons parcourir ce qui peut être amélioré et comment éviter les re-rendus de composants inutiles.
Depuis la version 16.3, React dispose d’une version stable de l’API de contexte qui peut être utilisée pour partager facilement des données entre de nombreuses Composants. Il peut être transmis directement aux composants qui en ont besoin tout en évitant le forage par hélice. Dans cet article, vous apprendrez à utiliser efficacement Context sans créer de goulots d'étranglement au niveau des performances.
Imaginez que vous disposiez d'une application dotée d'une fonction spinner globale qui affiche une superposition couvrant toute la page lorsqu'une application communique avec un serveur. Une fonction permettant d’afficher et de masquer une visière doit être accessible à partir de n’importe quel composant de l’application.
Commençons par une implémentation simple, puis nous allons parcourir comment l’améliorer. Commencez par créer un nouveau projet avec create-react-app
. Si vous ne le savez pas, c’est un outil de la CLI pour l’échafaudage de projets React. Assurez-vous que Node.js est installé sur votre ordinateur. Si vous rencontrez des problèmes lors de la création d'un projet, consultez le site officiel – https://create-react-app.dev/ .
npx créer-réagir-application context-app
Lorsque le projet est prêt, nous devons créer quelques fichiers.
Dans le fichier GlobalSpinnerContext.js, nous allons créer notre logique de contexte et notre fournisseur GlobalSpinnerContext, tandis que le dossier GlobalSpinner contient le composant et les styles Spinner. Le fichier RandomComments.js récupérera les commentaires d'une API et déclenchera GlobalSpinner en cas de besoin. src / components / RandomComments.js Le composant RandomComments affichera une liste de commentaires. Lorsqu'il est monté, il appelle API pour obtenir des commentaires, puis utilise {name} {body} src / components / GlobalSpinner / GlobalSpinner.js Composant simple avec superposition et texte Chargement ... src / components / GlobalSpinner / globalSpinner.css Mise en forme du texte superposé et du texte chargé. src / App.js Importe et rend GlobalSpinner et RandomComments. Si vous exécutez votre projet avec la commande Après avoir créé les fichiers nécessaires et mis à jour le fichier App.js, accédez au fichier GlobalSpinnerContext.js. [19659033] importer React, {createContext} depuis 'react' const GlobalSpinnerContext = createContext () export par défaut GlobalSpinnerContext src / contexte / GlobalSpinnerContext.js
src / components / GlobalSpinner / GlobalSpinner.js
src / components / GlobalSpinner / globalSpinner.css
src / components / RandomComments [1965] Naive Implementation
setComments
pour mettre à jour l'état et les afficher. import React, {useState, useEffect} de 'react'
const RandomComments = props => {
const [comments, setComments] = useState ([])
useEffect (() => {
(async () => {
const result = wait fetch ('https://jsonplaceholder.typicode.com/comments')
données const = wait result.json ()
setComments (data)
}) ()
}, [])
revenir (
Loading
. Vous pouvez être plus chic si vous voulez. import React from 'react'
importer './globalSpinner.css'
const GlobalSpinner = props => {
revenir (
.global-spinner-overlay {
position: fixe;
en haut: 0;
gauche: 0;
à droite: 0;
en bas: 0;
couleur de fond: rgba (0, 0, 0, 0,3);
taille de la police: 30px;
Couleur blanche;
poids de police: gras;
affichage: flex;
justifier-contenu: centre;
align-items: centre;
}
import Réagissez à partir de 'react';
importer './App.css';
importer GlobalSpinner à partir de './components/GlobalSpinner/GlobalSpinner'
importer RandomComments à partir de './components/RandomComments'
fonction App () {
revenir (
npm run start
un fond gris apparaît avec le texte Chargement en cours
au centre. Nous n'allons pas nous laisser séduire par de belles fileuses, car ce que nous avons actuellement devrait suffire pour mettre en œuvre l'implémentation de contexte.
Il s'agit de la mise en œuvre la plus simple, dans laquelle nous créons un contexte puis l'exportons. Ce contexte pourrait être importé et utilisé dans App.js, comme indiqué dans l'image ci-dessous:
App.js
import Réagissez à partir de 'react';
importer './App.css';
importer GlobalSpinner à partir de './components/GlobalSpinner/GlobalSpinner'
importer GlobalSpinnerContext à partir de './context/GlobalSpinnerContext';
importer RandomComments à partir de './components/RandomComments'
fonction App () {
revenir (
)
}
exportation par défaut App;
Cependant, nous devrions également écrire une logique à états pour la casserole dans App.js. Créons plutôt un composant ContextProvider qui encapsulera cette logique et maintiendra le fichier App.js propre.
Dans GlobalSpinnerContext.js
nous allons créer un composant GlobalSpinnerContextProvider
. Notez que la constante GlobalSpinnerContext
n'est plus une exportation par défaut. Le ContextProvider utilisera le crochet useState
pour stocker et mettre à jour l'état de visibilité du disque. La première tentative de solution de travail pourrait ressembler à ceci:
import React, {useState, createContext} de 'react'
export const GlobalSpinnerContext = createContext ()
const GlobalSpinnerContextProvider = (props) => {
const [isGlobalSpinnerOn, setGlobalSpinner] = useState (false)
revenir (
{props.children}
)
}
export par défaut GlobalSpinnerContextProvider
N'oubliez pas de mettre à jour le fichier App.js car nous utilisons Context.Provider dans le fichier GlobalSpinnerContext.js.
App.js
import Réagissez à partir de 'react';
importer './App.css';
importer GlobalSpinner à partir de './components/GlobalSpinner/GlobalSpinner'
importer GlobalSpinnerContextProvider de './context/GlobalSpinnerContext';
importer RandomComments à partir de './components/RandomComments'
fonction App () {
revenir (
)
}
exportation par défaut App;
Ensuite, dans le composant GlobalSpinner
vous pouvez importer le crochet GlobalSpinnerContext
et l'utiliser avec useContext
.
GlobalSpinner.js
importer Réagir, {useContext} à partir de 'réagir'
importer './globalSpinner.css'
importer {GlobalSpinnerContext} de '../../context/GlobalSpinnerContext'
const GlobalSpinner = props => {
const {isGlobalSpinnerOn} = useContext (GlobalSpinnerContext)
retourner isGlobalSpinnerOn? (
Chargement ...
): null
}
export GlobalSpinner par défaut
Si vous consultez le site Web, vous verrez que la superposition avec la roulette a disparu. Cela est dû au fait que nous avons défini la valeur de rotation sur false
par défaut. De la même manière, nous pouvons importer et utiliser le GlobalSpinnerContext
dans le composant RandomComments
. Cependant, cette fois-ci, nous n'avons pas besoin de la valeur isGlobalSpinnerOn
mais d'un accès à la fonction setGlobalSpinner
.
RandomComments.js
importer React, {useState, useEffect, useContext} à partir de 'react'
importer {GlobalSpinnerContext} de '../context/GlobalSpinnerContext'
const RandomComments = props => {
const [comments, setComments] = useState ([])
const {setGlobalSpinner} = useContext (GlobalSpinnerContext)
useEffect (() => {
(async () => {
setGlobalSpinner (true)
const result = wait fetch ('https://jsonplaceholder.typicode.com/comments')
données const = wait result.json ()
setComments (data)
setGlobalSpinner (false)
}) ()
}, [setGlobalSpinner])
revenir (
{comments.map (comment => {
const {nom, corps, id} = commentaire
revenir (
{name}
{body}
)
})}
)
}
export par défaut RandomComments
Ceci est une implémentation très simple qui fonctionne pour ce scénario, mais il présente des problèmes.
Améliorations apportées au contexte GlobalSpinner
Le premier numéro porte sur la façon dont nous passons à GlobobalSpinnerOn
et setGlobalSpinner
au fournisseur.
{props.children}
Tous les consommateurs de contexte sont restitués chaque fois qu'une valeur transmise au fournisseur change
. Cela signifie que si nous changeons la visibilité du spinner ou d'un composant parent restitué, les composants GlobalSpinner et RandomComments seront rendus de nouveau. En effet, nous créons un nouvel objet en ligne pour la valeur de fournisseur. Une façon de résoudre ce problème consiste à utiliser le crochet useMemo
qui mémoriserait l'objet de valeur. Il ne serait recréé que lorsque la valeur isGlobalSpinnerOn
change.
import React, {useState, createContext, useMemo} à partir de 'react'
export const GlobalSpinnerContext = createContext ()
const GlobalSpinnerContextProvider = (props) => {
const [isGlobalSpinnerOn, setGlobalSpinner] = useState (false)
valeur const = useMemo (() => ({
isGlobalSpinnerOn,
setGlobalSpinner
}), [isGlobalSpinnerOn])
revenir (
{props.children}
)
}
export par défaut GlobalSpinnerContextProvider
Cela corrige le problème de la recréation d'un nouvel objet à chaque rendu et donc du rendu de tous les consommateurs. Malheureusement, nous avons toujours un problème.
Éviter le re-rendu de tous les consommateurs de contexte
Comme nous l’avons maintenant, un nouvel objet de valeur sera créé chaque fois que la visibilité de spinner changera. Cependant, bien que le composant GlobalSpinner s'appuie sur la fonction isGlobalSpinnerOn
il ne s'appuie pas sur la fonction setGlobalSpinner
. De même, RandomComments requiert uniquement l'accès à la fonction setGlobalSpinner
. Par conséquent, il n’a pas de sens de demander à RandomComments de restituer chaque rendu de visibilité de la visière, car le composant n'en dépend pas directement. Par conséquent, pour éviter ce problème, nous pouvons créer un autre contexte afin de séparer isGlobalSpinnerOn
et setGlobalSpinner
.
import React, {useState, createContext} de 'react'
export const GlobalSpinnerContext = createContext ()
export const GlobalSpinnerActionsContext = createContext ()
const GlobalSpinnerContextProvider = (props) => {
const [isGlobalSpinnerOn, setGlobalSpinner] = useState (false)
revenir (
{props.children}
)
}
export par défaut GlobalSpinnerContextProvider
Grâce à deux fournisseurs de contexte, les composants peuvent consommer exactement ce dont ils ont besoin. Maintenant, nous devons mettre à jour les composants GlobalSpinner
et RandomComments
pour utiliser des valeurs correctes.
GlobalSpinner.js
Le seul changement que nous ne déstructurons pas isGlobalSpinnerOn
.
importer Réagir, {useContext} à partir de 'réagir'
importer './globalSpinner.css'
importer {GlobalSpinnerContext} de '../../context/GlobalSpinnerContext'
const GlobalSpinner = props => {
const isGlobalSpinnerOn = useContext (GlobalSpinnerContext)
retourner isGlobalSpinnerOn? (
Chargement ...
): null
}
export GlobalSpinner par défaut
RandomComments.js
Nous importons «GlobalSpinnerActionsContext» au lieu de «GlobalSpinnerContext». De plus, nous ne détruisons plus la fonction ‘setGlobalSpinner’.
import React, {useState, useEffect, useContext} de 'react'
importer {GlobalSpinnerActionsContext} de '../context/GlobalSpinnerContext'
const RandomComments = props => {
const [comments, setComments] = useState ([])
const setGlobalSpinner = useContext (GlobalSpinnerActionsContext)
useEffect (() => {
(async () => {
setGlobalSpinner (true)
const result = wait fetch ('https://jsonplaceholder.typicode.com/comments')
données const = wait result.json ()
setComments (data)
setGlobalSpinner (false)
}) ()
}, [setGlobalSpinner])
Nous avons résolu avec succès notre problème de performance. Cependant, des améliorations peuvent encore être apportées. Cependant, il ne s'agit pas de performances, mais de la manière dont nous utilisons les valeurs de contexte.
Consommer le contexte à votre aise
Pour utiliser des valeurs de contexte spinner dans n'importe quel composant, nous devons importer le contexte directement, ainsi que le crochet useContext
. Nous pouvons le rendre un peu moins fastidieux en utilisant un wrapper pour l'appel de hook useContext
. Allez dans le fichier GlobalSpinnerContext.js
. Nous n'exporterons plus directement les valeurs de contexte, mais des fonctions personnalisées permettant de consommer des contextes.
GlobalSpinnerContext.js
import React, {useState, createContext, useContext} de 'react'
const GlobalSpinnerContext = createContext ()
const GlobalSpinnerActionsContext = createContext ()
export const useGlobalSpinnerContext = () => useContext (GlobalSpinnerContext)
export const useGlobalSpinnerActionsContext = () => useContext (GlobalSpinnerActionsContext)
const GlobalSpinnerContextProvider = (props) => {
const [isGlobalSpinnerOn, setGlobalSpinner] = useState (false)
revenir (
{props.children}
)
}
export par défaut GlobalSpinnerContextProvider
Nous devons ensuite mettre à jour les GlobalSpinner
et RandomComments
et remplacer l'utilisation directe du useContext
en faveur des fonctions wrapper.
GlobalSpinner .js
import Réagit de 'réagit'
importer './globalSpinner.css'
importer {useGlobalSpinnerContext} de '../../context/GlobalSpinnerContext'
const GlobalSpinner = props => {
const isGlobalSpinnerOn = useGlobalSpinnerContext ()
retourner isGlobalSpinnerOn? (
Chargement ...
): null
}
export GlobalSpinner par défaut
RandomComments.js
import Réagissez, {useState, useEffect} à partir de 'react'
importer {useGlobalSpinnerActionsContext} de '../context/GlobalSpinnerContext'
const RandomComments = props => {
const [comments, setComments] = useState ([])
const setGlobalSpinner = useGlobalSpinnerActionsContext ()
useEffect (() => {
(async () => {
setGlobalSpinner (true)
const result = wait fetch ('https://jsonplaceholder.typicode.com/comments')
données const = wait result.json ()
setComments (data)
setGlobalSpinner (false)
}) ()
}, [setGlobalSpinner])
Nous n’avons plus besoin d’importer directement les contextes useContext
et spinner. Au lieu de cela, nous avons une interface pour utiliser ces valeurs. Nous pouvons apporter une autre amélioration utile. useContext
ne devrait être appelé qu'à l'intérieur d'un contexte (fournisseur)
. Pour nous assurer que nous ne commettons pas l'erreur d'utiliser un contexte en dehors d'un fournisseur
nous pouvons vérifier s'il existe une valeur de contexte.
import React, {useState, createContext, useContext} from 'rea '
const GlobalSpinnerContext = createContext ()
const GlobalSpinnerActionsContext = createContext ()
export const useGlobalSpinnerContext = () => {
const context = useContext (GlobalSpinnerContext)
if (contexte === non défini) {
jeter une nouvelle erreur (`useGlobalSpinnerContext doit être appelé dans GlobalSpinnerContextProvider`)
}
contexte de retour
}
export const useGlobalSpinnerActionsContext = () => {
const context = useContext (GlobalSpinnerActionsContext)
if (contexte === non défini) {
jeter une nouvelle erreur (`useGlobalSpinnerActionsContext doit être appelé dans GlobalSpinnerContextProvider`)
}
contexte de retour
}
Comme vous pouvez le voir sur l'image ci-dessus, au lieu de renvoyer immédiatement le résultat de useContext
nous vérifions d'abord la valeur du contexte. S'il n'est pas défini, une erreur est générée. Néanmoins, il serait un peu répétitif de le faire pour chaque fonction de consommateur useContext
alors résumons-le en fonction d'usine réutilisable.
import React, {useState, createContext, useContext} de 'react'
const GlobalSpinnerContext = createContext ()
const GlobalSpinnerActionsContext = createContext ()
/ * eslint-disable * /
const useContextFactory = (nom, contexte) => {
return () => {
const ctx = useContext (context)
if (ctx === non défini) {
throw new Error (`use $ {name} Le contexte doit être utilisé avec un $ {name} ContextProvider.`)
}
retour ctx
}
}
/ * eslint-enable * /
export const useGlobalSpinnerContext = useContextFactory ('GlobalSpinnerContext', GlobalSpinnerContext)
export const useGlobalSpinnerActionsContext = useContextFactory ('GlobalSpinnerActionsContext', GlobalSpinnerActionsContext)
La fonction useContextFactory
accepte le paramètre name
qui sera utilisé dans un message d'erreur et le paramètre context
qui sera consommé. Vous devrez peut-être désactiver eslint pour useContextFactory
car cela pourrait générer une erreur indiquant que useContext
ne peut pas être appelé à l'intérieur d'un rappel. Cette erreur eslint est renvoyée car la fonction useContextFactory
commence par le mot use
qui est réservé aux hooks. Vous pouvez renommer la fonction en quelque chose comme factoryUseContext
.
Dans cet article, nous avons expliqué comment utiliser et utiliser correctement le contexte tout en évitant les goulots d'étranglement en termes de performances. Vous pouvez trouver un rapport GitHub pour ce projet à l’adresse https://github.com/ThomasFindlay/react-using-context-api-right-way .
Source link