
TL; DR
Context et Redux sont-ils identiques?
Non. Ce sont des outils différents qui font des choses différentes et sont utilisés à des fins différentes.
Le contexte est-il un outil de «gestion d'état»?
Non. Le contexte est une forme d'injection de dépendances. C'est un mécanisme de transport qui ne contrôle rien. Toute "gestion d'état" est effectuée manuellement, généralement en utilisant les hooks useState () / useReducer ().
Context et useReducer () remplacent-ils Redux?
Non. Ils sont quelque peu similaires et se chevauchent partiellement, mais diffèrent considérablement en termes de capacités.
Quand devriez-vous utiliser le contexte?
Lorsque vous souhaitez rendre certaines données disponibles à plusieurs composants, mais que vous ne souhaitez pas transmettre ces données comme accessoires à tous les niveaux de l'arborescence des composants.
Quand devriez-vous utiliser Context et useReducer ()?
Lorsque vous devez gérer l'état d'un composant moyennement complexe dans une partie spécifique de votre application.
Quand devriez-vous utiliser Redux?
Redux est le plus utile lorsque:
- Grand nombre de composants avec état utilisant les mêmes données
- L'état de l'application est mis à jour fréquemment
- Logique complexe pour la mise à jour de l'état
- L'application a une base de code moyenne à grande et de nombreuses personnes y travaillent
- Vous voulez savoir quand, pourquoi et comment l'état de l'application est mis à jour et être en mesure de visualiser ces changements
- Vous avez besoin de capacités plus puissantes pour gérer les effets secondaires, la stabilité (persistance) et la sérialisation des données
Comprendre le contexte et Redux
Pour utiliser correctement l'outil, il est essentiel de comprendre:
- Pourquoi est-ce
- Quelles tâches résout-il
- Quand et pourquoi a-t-il été créé
Il est également important de comprendre les problèmes que vous essayez de résoudre dans votre application et d'utiliser les outils qui les résolvent le mieux, non pas parce que quelqu'un vous a dit de les utiliser, et non parce qu'ils sont populaires, mais parce qu'ils fonctionnent le mieux pour vous dans un situation particulière.
La confusion autour de Context et Redux est principalement due à une mauvaise compréhension de ce à quoi ces outils sont destinés et des tâches qu'ils résolvent. Par conséquent, avant de parler du moment où ils doivent être utilisés, il est nécessaire de déterminer ce qu'ils sont et les problèmes qu'ils résolvent.
Qu'est-ce que le contexte?
Commençons par définir le contexte à partir de la documentation officielle :
«Le contexte permet de transmettre des données à travers l'arborescence des composants sans avoir à passer des accessoires à des niveaux intermédiaires.
Dans une application React typique, les données sont transmises de haut en bas (du parent à l'enfant) à l'aide d'accessoires. Cependant, cette méthode peut être excessive pour certains types d'accessoires (par exemple, la langue sélectionnée, le thème d'interface) qui doivent être transmis à de nombreux composants d'une application. Le contexte fournit un moyen de distribuer ces données entre les composants sans avoir à transmettre explicitement des accessoires à chaque niveau de l'arborescence des composants. "
Veuillez noter que cette définition ne dit pas un mot sur «gestion», seulement sur «transfert» et «distribution».
L'API de contexte actuelle (React.createContext ()) a été introduite pour la première fois dans React 16.3 en remplacement de l'ancienne API disponible dans les versions antérieures de React, mais avec plusieurs défauts de conception. L'un des principaux problèmes était que les mises à jour des valeurs transmises via le contexte pouvaient être "bloquées" si le composant ignorait le rendu via shouldComponentUpdate (). Étant donné que de nombreux composants ont eu recours à shouldComponentUpdate () à des fins d'optimisation, le passage des données à travers le contexte est devenu inutile. createContext () a été conçu pour résoudre ce problème, donc toute mise à jour de la valeur sera reflétée dans les composants enfants, même si le composant intermédiaire ignore le rendu.
Utiliser le contexte
L'utilisation du contexte dans une application suppose ce qui suit:
- Appelez const MyContext = React.createContext () pour instancier l'objet de contexte
- Dans le composant parent, rendez & ltMyContext.Provider value = {someValue}>. Cela met certaines données en contexte. Ces données peuvent être n'importe quoi: chaîne, nombre, objet, tableau, instance de classe, gestionnaire d'événements, etc.
- Obtenez la valeur de contexte dans n'importe quel composant à l'intérieur du fournisseur en appelant const theContextValue = useContext (MyContext)
Lorsque le composant parent est mis à jour et que la nouvelle référence est passée en tant que valeur de fournisseur, tout composant qui «consomme» le contexte sera forcé d'être mis à jour.
En règle générale, la valeur de contexte est l'état du composant:
import { createContext } from 'react'
export const MyContext = createContext()
export function ParentComponent({ children }) {
const [counter, setCounter] = useState(0)
return (
<MyContext.Provider value={[counter, setCounter]}>
{children}
</MyContext.Provider>
)
}
Le composant enfant peut alors appeler le hook useContext () et lire la valeur de contexte:
import { useContext } from 'react'
import { MyContext } from './MyContext'
export function NestedChildComponent() {
const [counter, setCounter] = useContext(MyContext)
// ...
}
On voit que le contexte ne contrôle vraiment rien. Au lieu de cela, c'est une sorte de pipe. Vous placez les données au début (en haut) du tunnel à l'aide de <MyContext.Provider>, puis ces données sont reportées jusqu'à ce que le composant les demande à l'aide de useContext (MyContext).
Ainsi, l'objectif principal du contexte est d'empêcher le forage d'étai. Au lieu de passer des données comme accessoires à chaque niveau de l'arborescence des composants, tout composant imbriqué dans <MyContext.Provider> peut y accéder via useContext (MyContext). Cela élimine le besoin d'écrire du code pour implémenter la logique de passage prop.
Conceptuellement, il s'agit d'une forme d'injection de dépendance... Nous savons que l'enfant a besoin de données d'un certain type, mais il n'essaye pas de créer ou de définir ces données par lui-même. Au lieu de cela, il s'appuie sur un ancêtre pour transmettre ces données au moment de l'exécution.
Qu'est-ce que Redux?
Voici ce que dit la définition de Redux Basics :
«Redux est un modèle de conception et une bibliothèque pour gérer et mettre à jour l'état des applications à l'aide d'événements appelés opérations. Redux agit comme un référentiel centralisé de l'état des applications, suivant des règles pour garantir des mises à jour d'état prévisibles.
Redux vous permet de gérer l'état "global" - état qui est redirigé vers plusieurs parties de votre application.
Les modèles et les outils fournis par Redux permettent de déterminer plus facilement où, quand, pourquoi et comment l'état a été mis à jour et comment l'application a répondu à ce changement. »
Veuillez noter que cette description indique:
- Gestion d'état
- Le but de Redux est de déterminer pourquoi et comment un changement d'état s'est produit
Redux était à l'origine une implémentation de «l'architecture Flux» , un design pattern développé par Facebook en 2014, un an après la sortie de React. Depuis l'avènement de Flux, la communauté a développé de nombreuses bibliothèques qui implémentent ce concept de différentes manières. Redux est apparu en 2015 et est rapidement devenu le gagnant de ce concours grâce à sa conception réfléchie, à la résolution de problèmes courants et à son excellente compatibilité avec React.
Du point de vue architectural, Redux utilise avec insistance les principes de la programmation fonctionnelle, qui vous permet d'écrire du code sous la forme de «réducteurs» prévisibles, et de séparer l'idée de «ce qui s'est passé» de la logique qui définit «comment l'état est mis à jour lorsque cela l'événement se produit. " Redux utilise également un middleware pour étendre les capacités du magasin, y compris la gestion des effets secondaires .
Redux fournit également des outils de développement pour explorer l'historique des opérations et les changements d'état au fil du temps.
Redux et React
Redux lui-même est indépendant de l'interface utilisateur - vous pouvez l'utiliser avec n'importe quelle couche de vue (React, Vue, Angular, vanilla JS, etc.) ou aucune interface utilisateur du tout.
Le plus souvent, cependant, Redux est utilisé en conjonction avec React. La bibliothèque React Redux est la couche de liaison officielle de l'interface utilisateur qui permet aux composants React d'interagir avec le magasin Redux en récupérant des valeurs de l'état Redux et en lançant des opérations. React-Redux utilise le contexte en interne. Cependant, il convient de noter que React-Redux passe par le contexte une instance de magasin Redux, pas la valeur de l'état actuel!Voici un exemple d'utilisation du contexte pour l'injection de dépendances. Nous savons que nos composants connectés à Redux doivent interagir avec le magasin Redux, mais nous ne savons pas ou ne nous soucions pas de ce qu'est ce magasin lorsque nous définissons le composant. Le véritable magasin Redux est injecté dans l'arborescence lors de l'exécution à l'aide du composant <Provider> fourni par React-Redux.
Par conséquent, React-Redux peut également être utilisé pour empêcher le «forage» (en raison de l'utilisation interne du contexte). Au lieu de passer explicitement la nouvelle valeur via <MyContext.Provider>, nous pouvons placer ces données dans le magasin Redux, puis les récupérer dans le composant souhaité.
Objectif et cas d'utilisation de (React-) Redux
L'objectif principal de Redux selon la documentation officielle:
"Les modèles et les outils fournis par Redux permettent de mieux comprendre quand, où, pourquoi et comment un changement d'état s'est produit, et comment l'application y a réagi."
Il existe plusieurs autres raisons d'utiliser Redux. L'une de ces raisons est d'éviter le «forage».
Autres cas d'utilisation:
- Séparation complète de la logique de gestion des états et de la couche d'interface utilisateur
- Répartition de la logique de gestion d'état entre différentes couches d'interface utilisateur (par exemple, lors du processus de traduction d'une application d'AngularJS vers React)
- Utilisation du middleware Redux pour ajouter une logique supplémentaire lors de l'initialisation des opérations
- Possibilité de sauvegarder des parties de l'état Redux
- Possibilité de recevoir des rapports de bogues pouvant être reproduits par d'autres développeurs
- Capacité à déboguer rapidement la logique et l'interface utilisateur pendant le développement
Dan Abramov a énuméré ces cas dans son article de 2016 Pourquoi vous n'avez pas besoin de Redux .
Pourquoi le contexte n'est-il pas un outil de «gestion d'état»?
L'état correspond à toutes les données décrivant le comportement d'une application . Nous pouvons classer l'état en catégories telles que l'état du serveur, l'état de la communication et l'état local si nous le voulons, mais l'aspect clé est le stockage, la lecture, la mise à jour et l'utilisation des données.
David Khourshid, auteur de la bibliothèque XState et spécialiste de la gestion de l'État, a noté dans l'un de ses tweets que
«la gestion de l'État consiste à changer d'état au fil du temps».
Ainsi, on peut dire que "gestion de l'état" signifie ce qui suit:
- Stockage de la valeur initiale
- Obtenir la valeur actuelle
- Mettre à jour une valeur
En outre, il existe généralement un moyen d'être averti lorsque la valeur de l'état actuel a changé.
Les hooks React useState () et useReducer () sont d'excellents exemples de gestion d'état. Avec ces crochets, nous pouvons:
- Stocker la valeur initiale en appelant un hook
- Obtenez la valeur actuelle également en appelant le hook
- Mettez à jour la valeur en appelant respectivement setState () ou dispatch ()
- Soyez conscient des mises à jour de l'état en re-rendant le composant
Redux et MobX vous permettent également de gérer l'état:
- Redux stocke la valeur initiale en appelant le réducteur racine, permet de lire la valeur actuelle avec store.getState (), de mettre à jour la valeur avec store.dispatch (action) et de recevoir des notifications de mise à jour d'état via store.subscribe (écouteur)
- MobX préserve une valeur initiale en attribuant une valeur à un champ de classe de stockage, permet à la valeur actuelle d'être lue et mise à jour via les champs de stockage, et reçoit des notifications de mise à jour d'état à l'aide des méthodes autorun () et computed ()
Les outils de gestion d'état incluent même des outils de cache de serveur tels que React-Query, SWR, Apollo et Urql - ils stockent une valeur initiale basée sur les données extraites, renvoient la valeur actuelle à l'aide de hooks et permettent la mise à jour des valeurs via des "mutations de serveur" et notifient les changements en re-rendant le composant.
React Context ne répond pas aux critères nommés. Ce n'est donc pas un outil de gestion d'état
Comme indiqué précédemment, le contexte lui-même ne stocke rien. Le composant parent, qui rend <MyContext.Provider>, est chargé de transmettre la valeur, qui dépend généralement de l'état du composant. La vraie "gestion de l'état" provient des hooks useState () / useReducer ().
David Khourshid note également:
«Le contexte est la manière dont l'état existant est partagé entre les composants. Le contexte ne fait rien avec l'État. "
Et dans un tweet plus tard ,
"Je suppose que le contexte est comme des accessoires cachés cet état abstrait."
Tout ce que fait ce contexte est d'éviter le «forage».
Comparaison du contexte et de Redux
Comparons les capacités du contexte et de React + Redux:
- Le contexte
-
- Ne stocke rien et ne gère rien
- Fonctionne uniquement dans les composants React
- Passe en dessous d'une valeur simple (unique), qui peut être n'importe quoi (primitif, objet, classe, etc.)
- Permet de lire ce sens simple
- Peut être utilisé pour empêcher le "perçage"
- Affiche la valeur actuelle des composants Fournisseur et Consommateur dans les outils de développement, mais n'affiche pas l'historique des modifications apportées à cette valeur
- Met à jour les composants consommant lorsque la valeur change, mais ne permet pas d'ignorer la mise à jour
- Ne fournit aucun mécanisme pour gérer les effets secondaires - uniquement responsable du rendu
- Réagir + Redux
-
- Stocke et gère une valeur simple (généralement un objet)
- Fonctionne avec n'importe quelle interface utilisateur ainsi qu'en dehors des composants React
- Permet de lire ce sens simple
- Peut être utilisé pour empêcher le "perçage"
- Peut mettre à jour la valeur en initialisant les opérations et en exécutant des réducteurs
- Les outils de développement affichent l'historique de l'initialisation des opérations et des changements d'état
- Fournit la possibilité d'utiliser un middleware pour gérer les effets secondaires
- Permet aux composants de s'abonner aux mises à jour du magasin, de récupérer des parties spécifiques de l'état du magasin et de contrôler le rendu des composants
De toute évidence, ce sont des outils complètement différents avec des capacités différentes. Le seul point d'intersection entre eux est d'empêcher le "perçage".
Contexte et utilisationReducer ()
Un des problèmes avec la discussion «Contexte contre Redux» est que les gens veulent souvent dire: «J'utilise useReducer () pour gérer l'état et le contexte pour transmettre de la valeur». Mais au lieu de cela, ils disent simplement: «J'utilise le contexte». C'est, à mon avis, la principale raison de la confusion qui contribue au maintien du mythe selon lequel le contexte «contrôle l'État».
Considérez la combinaison de Context + useReducer (). Oui, cette combinaison ressemble beaucoup à Redux + React-Redux. Ces deux combinaisons ont:
- Valeur stockée
- Fonction réducteur
- Possibilité d'initialiser les opérations
- La possibilité de transmettre une valeur et de la lire dans des composants imbriqués
Cependant, il existe encore des différences importantes entre eux, qui se manifestent dans leurs capacités et leur comportement. J'ai noté ces différences dans React, Redux et Context Behavior et le (presque) guide complet du rendu dans React . En résumé, on peut noter ce qui suit:
- Context + useReducer () repose sur le passage de la valeur actuelle à travers le contexte. React-Redux passe l'instance actuelle du magasin Redux à travers le contexte
- Cela signifie que lorsque useReducer () produit une nouvelle valeur, tous les composants abonnés au contexte sont obligés de se redessiner, même s'ils n'utilisent qu'une partie des données. Cela peut entraîner des problèmes de performances en fonction de la taille de la valeur d'état, du nombre de composants signés et de la fréquence de re-rendu. Avec React-Redux, les composants peuvent s'abonner à une partie spécifique de la valeur de magasin et ne redessiner que lorsque cette partie change
Il existe d'autres différences importantes:
- Context + useReducer () sont des capacités intégrées de React et ne peuvent pas être utilisées en dehors de celui-ci. Le magasin Redux est indépendant de l'interface utilisateur, il peut donc être utilisé séparément de React
- React DevTools , . Redux DevTools , ( , type and payload),
- useReducer() middleware. useEffect() useReducer(), useReducer() middleware, Redux middleware
Voici ce que Sebastian Markbage (React Core Team Architect) a dit à propos de l'utilisation du contexte :
«Mon opinion personnelle est que le nouveau contexte est prêt à être utilisé pour des mises à jour à basse fréquence improbables (telles que la localisation ou le thème). Il peut également être utilisé dans tous les cas où l'ancien contexte a été utilisé, i.e. pour les valeurs statiques avec distribution ultérieure de la mise à jour par abonnement. Il n'est pas prêt à être utilisé en remplacement des distributeurs d'État de type Flux. "
De nombreux articles sur le Web recommandent de configurer plusieurs contextes distincts pour différentes parties de l'état, évitant ainsi les rendus inutiles et résolvant les problèmes de portée. Certains articles suggèrent également d' ajouter vos propres «composants contextuels» , ce qui nécessite une combinaison de React.memo (), useMemo (), et de diviser soigneusement le code en deux contextes pour chaque partie de l'application (un pour les données, un pour fonctions de mise à jour). Bien sûr, vous pouvez écrire du code de cette façon, mais dans ce cas, vous réinventez React-Redux.
Donc, alors que Context + useReducer () est une alternative légère à Redux + React-Redux en première approximation ... ces combinaisons ne sont pas identiques, context + useReducer () ne peut pas remplacer complètement Redux!
Choisir le bon outil
Afin de choisir le bon outil, il est très important de comprendre les tâches que l'outil résout, ainsi que les tâches auxquelles vous êtes confronté.
Présentation des cas d'utilisation
- Le contexte
- Transfert de données vers des composants imbriqués sans "perçage"
- useReducer ()
- Contrôle de l'état d'un composant complexe à l'aide d'une fonction de réduction
- Contexte + useReducer ()
- Gestion de l'état d'un composant complexe à l'aide d'une fonction de réduction et transfert de l'état aux composants imbriqués sans "perçage"
- Redux
- Contrôle d'un état très complexe avec des fonctions réductrices
- Traçabilité de quand, pourquoi et comment l'état a changé au fil du temps
- Désir d'isoler complètement la logique de gestion d'état de la couche d'interface utilisateur
- Distribution de la logique de gestion d'état entre différentes couches d'interface utilisateur
- Utilisation des capacités du middleware pour implémenter une logique supplémentaire lors de l'initialisation des opérations
- La possibilité de sauver certaines parties de l'État
- Possibilité d'obtenir des rapports d'erreur reproductibles
- Capacité à déboguer rapidement la logique et l'interface utilisateur pendant le développement
- Redux + React-Redux
- Tous les cas d'utilisation de Redux + la possibilité pour les composants React d'interagir avec le magasin Redux
Encore une fois: les outils nommés résolvent différents problèmes!
Recommandations
Alors, comment décidez-vous quoi utiliser?
Pour ce faire, il vous suffit de déterminer quel outil résout le mieux les problèmes de votre application.
- Si vous souhaitez simplement éviter le "forage", utilisez le contexte
- , , + useReducer()
- , , .., Redux + React-Redux
Je pense que si votre application a 2-3 contextes pour gérer l'état, vous devriez passer à Redux.
Vous entendrez souvent que «l'utilisation de Redux implique l'écriture de beaucoup de code standard», cependant, «Redux moderne» le rend beaucoup plus facile à apprendre et à utiliser. La boîte à outils officielle Redux résout le problème de création de modèles, et les hooks React-Redux facilitent l'utilisation de Redux dans les composants React.
Bien sûr, l'ajout de RTK et React-Redux en tant que dépendances augmente le bundle d'applications sur le contexte + useReducer (), qui sont intégrés. Mais les avantages de cette approche couvrent les inconvénients - meilleur traçage des états, logique plus simple et plus prévisible, optimisation améliorée du rendu des composants.
Il est également important de noter que l'un n'exclut pas l'autre - vous pouvez utiliser Redux, Context et useReducer () ensemble. Nous vous recommandons de stocker l'état "global" dans Redux et l'état local dans les composants, et d'être prudent lorsque vous déterminez quelle partie de l'application doit être stockée dans Redux et laquelle dans les composants.... Vous pouvez donc utiliser Redux pour stocker l'état global, Context + useReducer () pour stocker l'état local et Context pour stocker des valeurs statiques, le tout simultanément dans la même application.
Encore une fois, je ne dis pas que tout l'état de l'application doit être stocké dans Redux, ou que Redux est toujours la meilleure solution. Mon point est que Redux est un bon choix, il existe de nombreuses raisons d'utiliser Redux et les frais d'utilisation ne sont pas aussi élevés que beaucoup le pensent.
Enfin, le contexte et Redux ne sont pas uniques. Il existe de nombreux autres outils qui traitent d'autres aspects de la gestion de l'État. MobX est une solution populaire qui utilise la POO et les observables pour mettre à jour automatiquement les dépendances. Parmi les autres approches du renouvellement de l'État, citons Jotai, Recoil et Zustand. Les bibliothèques de données telles que React Query, SWR, Apollo et Urql fournissent des abstractions qui facilitent l'utilisation de modèles communs pour travailler avec l'état mis en cache sur le serveur (une bibliothèque similaire ( RTK Query ) apparaîtra bientôt pour le Redux Toolkit).
J'espère que cet article vous a aidé à comprendre la différence entre le contexte et Redux, et quel outil doit être utilisé et quand. Merci de votre attention.