Explorer les transformateurs d’état – et d’autres alternatives – pour gérer l’état dans une application React
Dans les applications React modernes, un état de gestion peut souvent devenir complexe et peu maniable à mesure que l’application grandit en taille et en fonctionnalités. Une solution populaire à ce problème consiste à utiliser une bibliothèque de gestion d’état telle que Redux. Cependant, Redux peut également introduire ses difficultés techniques et ses inconvénients, tels que le besoin de beaucoup de code passe-partout et une courbe d’apprentissage abrupte pour les nouveaux développeurs sur un projet.
Dans cet article, nous explorerons des approches alternatives pour gérer l’état dans une application React qui n’utilise pas Redux. Nous montrerons également comment créer une gestion d’état robuste et évolutive sans dépendre d’une bibliothèque externe.
Dans cet exemple, nous allons créer une application de tâches simple pour montrer comment mettre en œuvre une gestion d’état robuste et évolutive dans une application React sans utiliser Redux.
Pour gérer l’état de notre application, nous allons définir quelques types. Le User
type représente l’utilisateur actuellement connecté. Le ToDo
type représente un élément de tâche unique, avec des champs pour le titre, la description, la date de création et la date d’achèvement. Le Maybe
type est un type utilitaire qui représente une valeur pouvant être undefined
, null
ou une valeur définie.
Le ToDoState
type représente l’état de la liste de tâches, y compris la liste des tâches et un formulaire partiellement rempli pour créer de nouvelles tâches. Finalement, le AppState
type représente l’état général de l’application, y compris l’utilisateur et la liste de tâches.
Voici nos types d’état :
export type User = {
username: string;
email: string;
}export type ToDo = {
title: string
description: string
createdAt: Date
completedAt: Maybe<Date>
}
export type ToDoState = {
list: ToDo[],
new: Maybe<Pick<ToDo, 'title'|'description'>>
}
type AppState = {
user: Maybe<User> // our app user type
todos: ToDoState // our app todos
}
Une approche pour gérer l’état dans une application React sans utiliser Redux consiste à utiliser un concept appelé transformateurs d’état. Un transformateur est une fonction autonome chargée de modifier une partie spécifique de l’état. Cela nous permet de séparer la logique de modification de l’état de l’application réelle du changement.
Pour définir nos transformateurs, nous devons créer quelques types d’utilitaires. Le premier type que nous définirons est le StateChange
type représente une fonction qui prend l’état actuel et renvoie une nouvelle version modifiée de l’état.
export type StateChange<ST = AppState> = (s: ST) => ST;
Ensuite, nous définirons la StateTransformer
type, qui représente une fonction qui prend certaines données et renvoie un StateChange
une fonction.
export type StateTransformer<ST, DT> = (d: DT) => StateChange<ST>;
Cela nous permet de séparer la logique de modification de l’état de l’application réelle du changement.
Le DeepPartial
type est un type utilitaire qui nous permettra de créer une version partielle d’un objet avec des propriétés imbriquées. Ceci est utile pour créer des formulaires partiellement remplis ou pour mettre à jour uniquement un sous-ensemble des propriétés d’un objet.
export type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
};
Enfin, nous définirons la StateTransformerFactory
type, une fonction d’ordre supérieur qui prend un objet de données « gauche » et « droite » et renvoie un StateTransformer
une fonction. Cela peut être utile pour créer des transformateurs d’état réutilisables qui peuvent être composés ensemble pour créer des changements d’état plus complexes.
export type StateTransformerFactory<ST, DT> = (
dl: DeepPartial<DT>
) => (dr: DeepPartial<DT>) => StateChange<ST>;
Avec ces types en place, nous pouvons maintenant commencer à définir des transformateurs pour modifier l’état de notre application.
Le Store
est le cœur de notre système de gestion de l’État. Il est chargé de conserver l’état actuel de l’application et de fournir une méthode pour modifier l’état. Dans cette section, nous verrons comment le Store
composant fonctionne et comment il peut être utilisé pour gérer l’état de notre application.
Le Store
est un composant wrapper qui fournit l’état et une méthode pour modifier l’état de ses enfants via l’API de contexte React. Examinons le code pour le Store
composant:
const Store = ({ children }: PropsWithChildren) => {
const [state, setState] = useState(defaultState);const mod: Mod = useCallback((...transformers) => {
setState((value) => transformers.reduce((acc, curr) => curr(acc), value));
}, []);
return (
<Context.Provider value={{ state, mod }}>
{children}
</Context.Provider>
);
};
Le Store
utilise le useState
crochet pour définir le state
variable et la setState
une fonction. Le state
est initialisé à l’état par défaut de l’application, qui est transmis en tant que prop. Le mod
fonction est définie à l’aide de la useCallback
hook, qui garantit qu’il n’est recréé que lorsque l’une de ses dépendances change. Le mod
La fonction applique une série de transformateurs à l’état en réduisant le tableau des transformateurs sur l’état actuel.
Le Store
crée également un fournisseur de contexte à l’aide de Context
objet créé à l’aide de la fonction React. Le Context.Provider
Le composant fournit l’état et le mod
fonction à ses enfants via le contexte.
Finalement, le Store
est exporté en tant qu’exportation par défaut afin de pouvoir être utilisé comme composant racine de l’application. Le useStore
hook est également exporté afin que les composants enfants puissent accéder à l’état et au mod
fonction du contexte.
Le Store
est essentiellement le « moteur » de notre système de gestion de l’État. Il contient l’état actuel de l’application et fournit une méthode pour le modifier à l’aide de transformateurs d’état.
Les transformateurs d’état sont des fonctions autonomes chargées de modifier une partie spécifique de l’état. Ils sont un élément clé de notre système de gestion de l’état, car ils nous permettent de séparer la logique de modification de l’état de l’application réelle du changement.
Définissons quelques transformateurs pour notre application de tâches afin de démontrer le fonctionnement des transformateurs d’état. Le premier transformateur que nous définirons est le setUserTransformer
qui définit le user
champ de l’état de l’application à une nouvelle valeur.
export const setUserTransformer: StateTransformer<AppState, User> =
(d) => (s) => ({...s, user: d })
Le setUserTransformer
transformateur prend dans un User
objet sous forme de données et renvoie un StateChange
fonction qui modifie l’état. Le revenu StateChange
la fonction clone l’objet d’état actuel, définit le user
champ à la nouvelle valeur et renvoie l’état modifié.
Ensuite, nous définirons la setTodosTransformer
transformateur, qui règle le todos
champ de l’état de l’application à une nouvelle valeur.
export const setTodosTransformer: StateTransformer<AppState, ToDo[]> =
(d) => (s) => ({...s, todos: { ...s.todos, list: d } })
Le setTodosTransformer
transformateur prend dans un ToDo[]
objet sous forme de données et renvoie un StateChange
fonction qui modifie l’état. Le revenu StateChange
la fonction clone l’objet d’état actuel, définit le list
domaine de la todos
champ à la nouvelle valeur et renvoie l’état modifié.
Enfin, nous définirons la setActiveTodoTransformer
transformateur, qui règle le new
domaine de la todos
champ dans l’état de l’application à une nouvelle valeur.
export const setActiveTodoTransformer: StateTransformer<AppState, Pick<ToDo, 'title'|'description'>> =
(d) => (s) => ({...s, todos: {...s.todos, new: d}})
Le setActiveTodoTransformer
transformateur prend dans un Pick<ToDo, 'title'|'description'>
objet sous forme de données et renvoie un StateChange
fonction qui modifie l’état. Le revenu StateChange
la fonction clone l’objet d’état actuel définit le new
domaine de la todos
champ à la nouvelle valeur et renvoie l’état modifié.
Avec ces transformateurs en place, nous pouvons utiliser le mod
fonction de la Store
composant pour appliquer ces transformateurs à l’état de notre application. Par exemple, nous pourrions utiliser le setTodosTransformer
transformateur pour définir la liste complète des tâches ou le setActiveTodoTransformer
transformateur pour régler le new
champ de l’état de la liste de tâches.
Les transformateurs d’état sont un outil puissant pour gérer l’état de notre application. Ils nous permettent d’encapsuler la logique de modification de parties spécifiques de l’état dans des fonctions autonomes, ce qui facilite le test et la maintenance de notre code.
Maintenant que nous avons défini notre Store
composant et certains transformateurs d’état, nous pouvons les utiliser pour gérer l’état de notre application. Pour ce faire, nous devons envelopper notre composant racine avec le Store
composant.
Pour envelopper le composant racine avec le Store
composant, nous pouvons mettre à jour le index.tsx
dossier comme suit :
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import Store from "./store";ReactDOM.render(
<Store>
<App />
</Store>,
document.getElementById("root")
);
Maintenant que nous avons fourni le Store
composant à notre application, nous pouvons utiliser le useStore
hook pour accéder à l’état et au mod
fonction du contexte. Le useStore
hook est un hook personnalisé qui renvoie l’état et le mod
fonction du contexte. Il est défini dans le store
modules comme suit :
export const useStore = (): StoreCtx => useContext(Context);
Pour utiliser le useStore
hook, nous devons l’importer dans un composant et l’appeler. Le crochet renverra l’état et le mod
fonction, que nous pouvons utiliser pour accéder et modifier l’état de l’application.
Voici un exemple d’utilisation du useStore
crochet pour accéder à l’état de l’application :
import { useStore } from "./store";const TodoList = () => {
const { state } = useStore();
const { todos } = state;
return (
<ul>
{todos.list.map((todo) => (
<li key={todo.title}>{todo.title}</li>
))}
</ul>
);
};
Maintenant que nous avons défini certains transformateurs d’état et appris comment accéder à l’état et au mod
fonction du contexte, voyons comment nous pouvons utiliser ces outils pour gérer l’état de notre application.
import { useStore } from "./store";
import { setTodosTransformer } from "./transformers/todos";import { useEffect } from "react";
const TODO_LIST = [
{
title: "Task 1",
description: "This is the first task",
createdAt: new Date(),
completedAt: new Date(),
},
{
title: "Task 2",
description: "This is the second task",
createdAt: new Date(),
completedAt: null,
},
{
title: "Task 3",
description: "This is the third task",
createdAt: new Date(),
completedAt: null,
},
];
export const App = () => {
useEffect(() => {
mod(setTodosTransformer(TODO_LIST));
}, [])
return (
<div className="app">
<h1>ToDo App</h1>
<TodoList />
</div>
)
}
L’un des avantages de l’utilisation du mod
transformateurs de fonction et d’état est qu’il nous permet d’optimiser les performances de notre application. Étant donné que tous les transformateurs du mod
fonction sont réduites sur l’état, le nouvel état est généré une fois que tous les transformateurs sont appliqués. Cela signifie que l’application ne sera restituée qu’à la fin de la mod
fonction au lieu d’après chaque transformateur.
Cela peut être particulièrement utile lorsque nous devons mettre à jour l’état plusieurs fois rapidement, par exemple lors de plusieurs appels d’API ou de la mise à jour de l’état en réponse à une entrée de l’utilisateur. En regroupant les mises à jour d’état à l’aide de mod
fonction, nous pouvons éviter les rendus inutiles et améliorer les performances de notre application.
Voici un exemple de la façon dont nous pouvons utiliser le mod
fonction pour regrouper les mises à jour d’état :
const { mod } = useStore();
mod(setTodosTransformer(todos), setActiveTodoTransformer(activeTodo));
En regroupant ces mises à jour d’état à l’aide de la mod
fonction, nous pouvons éviter les rendus inutiles et améliorer les performances de notre application.
En général, cependant, en utilisant le mod
Les transformateurs de fonction et d’état peuvent être un outil puissant pour gérer l’état de votre application et optimiser ses performances. En encapsulant la logique de modification de parties spécifiques de l’état dans des fonctions autonomes, vous pouvez écrire un code plus maintenable et testable et améliorer les performances globales de votre application.
Cet article nous a appris à créer un système de gestion d’état robuste et évolutif dans une application React sans utiliser de bibliothèques externes comme Redux. En utilisant le Store
composants et transformateurs d’état, nous avons pu encapsuler la logique de modification de parties spécifiques de l’état dans des fonctions autonomes et optimiser les performances de notre application en regroupant les mises à jour d’état à l’aide du mod
une fonction.
Nous avons également vu comment le Store
peut être utilisé dans des contextes globaux et locaux, selon les besoins de notre application. En définissant de nouveaux fournisseurs de contexte avec le même paradigme, nous pouvons utiliser le Store
composant et le useStore
crochet pour gérer l’état de parties spécifiques de notre application tout en conservant un magasin d’état global si nécessaire.
Alors que le système de gestion d’état que nous avons mis en place est déjà puissant et flexible, il existe plusieurs façons de l’optimiser et de l’étendre. En envisageant des stratégies telles que la persistance de l’état, la mise en cache de l’état, l’optimisation du mod
et en ajoutant un middleware, nous pouvons encore améliorer les performances et la flexibilité de notre système de gestion d’état et créer des applications React évolutives et maintenables.