Fermer

octobre 25, 2019

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.

src / contexte / GlobalSpinnerContext.js
src / components / GlobalSpinner / GlobalSpinner.js
src / components / GlobalSpinner / globalSpinner.css
src / components / RandomComments [1965] Naive Implementation

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 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 (
    
      {comments.map (comment => {         const {nom, corps, id} = commentaire         revenir (           

{name}

{body}

)       })}     
  ) } export par défaut RandomComments

src / components / GlobalSpinner / GlobalSpinner.js

Composant simple avec superposition et texte Loading . Vous pouvez être plus chic si vous voulez.

 import React from 'react'
importer './globalSpinner.css'

const GlobalSpinner = props => {
  revenir (
    

Chargement ...

) } export GlobalSpinner par défaut

src / components / GlobalSpinner / globalSpinner.css

Mise en forme du texte superposé et du texte chargé.

 .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;
}

src / App.js

Importe et rend GlobalSpinner et RandomComments.

 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 (
      
); } exportation par défaut App;

Si vous exécutez votre projet avec la commande 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.

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

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