Comment démarrer avec Redux pour la gestion de l'état JavaScript
Redux est un outil de gestion d'état, spécialement conçu pour les applications JavaScript côté client qui dépendent fortement de données complexes et d'API externes, et fournit d'excellents outils de développement qui facilitent le travail avec vos données.
Sommaire
Que fait Redux?
En termes simples, Redux est un magasin de données centralisé. Toutes les données de votre application sont stockées dans un seul grand objet. Les Redux Devtools facilitent la visualisation:
Cet état est immuable, ce qui est un concept étrange au début, mais qui a du sens pour plusieurs raisons. Si vous souhaitez modifier l'état, vous devez envoyer un action, qui prend essentiellement quelques arguments, forme une charge utile et l'envoie à Redux. Redux passe l'état actuel à un réducteur , qui modifie l'état existant et renvoie un nouvel état qui remplace l'état actuel et déclenche un rechargement des composants affectés. Par exemple, vous pouvez avoir un réducteur pour ajouter un nouvel élément à une liste, ou supprimer ou modifier un élément qui existe déjà.
En procédant de cette façon, vous n'obtiendrez jamais de comportement indéfini avec l'état de modification de votre application à volonté. De plus, comme il y a un enregistrement de chaque action et de ce qu'elle a changé, cela permet le débogage dans le temps, où vous pouvez faire défiler l'état de votre application pour déboguer ce qui se passe avec chaque action (un peu comme un historique git).
Redux peut être utilisé avec n'importe quel framework frontend, mais il est couramment utilisé avec React, et c'est ce sur quoi nous allons nous concentrer ici. Sous le capot, Redux utilise l'API Context de React, qui fonctionne de la même manière que Redux et convient aux applications simples si vous souhaitez renoncer complètement à Redux. Cependant, les Devtools de Redux sont fantastiques lorsque vous travaillez avec des données complexes, et ils sont en fait plus optimisés pour éviter les rediffusions inutiles.
Si vous utilisez TypeScript, les choses sont beaucoup plus compliquées pour que Redux soit strictement typé. Vous voudrez plutôt suivre ce guide, qui utilise typesafe-actions
pour gérer les actions et les réducteurs d'une manière conviviale.
Structurer votre projet
Tout d'abord, vous souhaiterez mettre en page la structure de vos dossiers. Cela dépend de vous et des préférences de style de votre équipe, mais il existe essentiellement deux modèles principaux que la plupart des projets Redux utilisent. La première consiste simplement à diviser chaque type de fichier (action, réducteur, middleware, effet secondaire) dans son propre dossier, comme ceci:
store/ actions/ reducers/ sagas/ middleware/ index.js
Ce n’est cependant pas le meilleur, car vous aurez souvent besoin à la fois d’un fichier d’action et d’un fichier de réduction pour chaque fonctionnalité que vous ajoutez. Il est préférable de fusionner les dossiers des actions et des réducteurs et de les diviser par fonctionnalité. De cette façon, chaque action et le réducteur correspondant sont dans le même fichier. Tu
store/ features/ todo/ etc/ sagas/ middleware/ root-reducer.js root-action.js index.js
Cela nettoie les importations, car maintenant vous pouvez importer les actions et les réducteurs dans la même instruction en utilisant:
import { todosActions, todosReducer } from 'store/features/todos'
C'est à vous de décider si vous souhaitez conserver le code Redux dans son propre dossier (/store
dans les exemples ci-dessus), ou intégrez-le dans le dossier racine src de votre application. Si vous séparez déjà le code par composant et que vous écrivez de nombreuses actions personnalisées et réducteurs pour chaque composant, vous souhaiterez peut-être fusionner les /features/
et /components/
et stockez les composants JSX avec le code réducteur.
Si vous utilisez Redux avec TypeScript, vous pouvez ajouter un fichier supplémentaire dans chaque dossier d'entités pour définir vos types.
Installation et configuration de Redux
Installez Redux et React-Redux depuis NPM:
npm install redux react-redux
Vous voudrez probablement aussi redux-devtools
:
npm install --save-dev redux-devtools
La première chose que vous voudrez créer est votre boutique. Enregistrez ceci sous /store/index.js
import { createStore } from 'redux' import rootReducer from './root-reducer' const store = createStore(rootReducer) export default store;
Bien sûr, votre boutique deviendra plus compliquée que cela à mesure que vous ajouterez des éléments tels que des addons d'effets secondaires, des intergiciels et d'autres utilitaires tels que connected-react-router
, mais c’est tout ce qui est nécessaire pour le moment. Ce fichier prend le réducteur racine et appelle createStore()
en l'utilisant, qui est exporté pour que l'application puisse l'utiliser.
Ensuite, nous allons créer une fonctionnalité simple de liste de tâches. Vous voudrez probablement commencer par définir les actions requises par cette fonctionnalité et les arguments qui leur sont transmis. Créer un /features/todos/
dossier et enregistrez ce qui suit sous types.js
:
export const ADD = 'ADD_TODO' export const DELETE = 'DELETE_TODO' export const EDIT = 'EDIT_TODO'
Cela définit quelques constantes de chaîne pour les noms d'action. Quelles que soient les données que vous transmettez, chaque action aura un type
property, qui est une chaîne unique qui identifie l'action.
Vous n'êtes pas obligé d'avoir un fichier de type comme celui-ci, car vous pouvez simplement taper le nom de chaîne de l'action, mais il est préférable pour l'interopérabilité de le faire de cette façon. Par exemple, vous pourriez avoir todos.ADD
et reminders.ADD
dans la même application, ce qui vous évite d'avoir à taper _TODO
ou _REMINDER
chaque fois que vous faites référence à une action pour cette fonctionnalité.
Ensuite, enregistrez ce qui suit sous /store/features/todos/actions.js
:
import * as types from './types.js' export const addTodo = text => ({ type: types.ADD, text }) export const deleteTodo = id => ({ type: types.DELETE, id }) export const editTodo = (id, text) => ({ type: types.EDIT, id, text })
Cela définit quelques actions en utilisant les types des constantes de chaîne, en présentant les arguments et la création de charge utile pour chacun. Celles-ci n'ont pas besoin d'être entièrement statiques, car ce sont des fonctions. Un exemple que vous pourriez utiliser est la définition d'un CUID d'exécution pour certaines actions.
Le morceau de code le plus compliqué, et où vous implémenterez la plupart de votre logique métier, se trouve dans les réducteurs. Celles-ci peuvent prendre plusieurs formes, mais la configuration la plus couramment utilisée est une instruction switch qui gère chaque cas en fonction du type d'action. Enregistrez ceci sous reducer.js
:
import * as types from './types.js' const initialState = ( { text: 'Hello World', id: 0 } ) export default function todos(state = initialState, action) { switch (action.type) { case types.ADD: return ( ...state, { id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1, text: action.text } ) case types.DELETE: return state.filter(todo => todo.id !== action.id ) case types.EDIT: return state.map(todo => todo.id === action.id ? { ...todo, text: action.text } : todo ) default: return state } }
L'état est passé en argument et chaque cas renvoie une version modifiée de l'état. Dans cet exemple, ADD_TODO
ajoute un nouvel élément à l'état (avec un nouvel identifiant à chaque fois), DELETE_TODO
supprime tous les éléments avec l'ID donné, et EDIT_TODO
mappe et remplace le texte de l'élément par l'ID donné.
L'état initial doit également être défini et transmis à la fonction de réduction comme valeur par défaut pour la variable d'état. Bien sûr, cela ne définit pas toute la structure d’états de Redux, seulement le state.todos
section.
Ces trois fichiers sont généralement séparés dans des applications plus complexes, mais si vous le souhaitez, vous pouvez également les définir tous dans un seul fichier, assurez-vous simplement que vous importez et exportez correctement.
Une fois cette fonctionnalité terminée, connectons-la à Redux (et à notre application). Dans /store/root-reducer.js
, importez le todosReducer (et tout autre réducteur de fonctionnalités du /features/
dossier), puis transmettez-le à combineReducers()
, formant un réducteur racine de niveau supérieur qui est transmis au magasin. C'est là que vous allez configurer l'état racine, en veillant à conserver chaque fonctionnalité sur sa propre branche.
import { combineReducers } from 'redux'; import todosReducer from './features/todos/reducer'; const rootReducer = combineReducers({ todos: todosReducer }) export default rootReducer
Utilisation de Redux dans React
Bien entendu, rien de tout cela n’est utile s’il n’est pas connecté à React. Pour ce faire, vous devrez encapsuler l'intégralité de votre application dans un composant Provider. Cela garantit que l'état et les hooks nécessaires sont transmis à chaque composant de votre application.
Dans App.js
ou index.js
, partout où vous avez votre fonction de rendu racine, enveloppez votre application dans un
, et passez-le au magasin (importé de /store/index.js
) comme accessoire:
import React from 'react'; import ReactDOM from 'react-dom'; // Redux Setup import { Provider } from 'react-redux'; import store, { history } from './store'; ReactDOM.render(, document.getElementById('root'));
Vous êtes désormais libre d'utiliser Redux dans vos composants. La méthode la plus simple consiste à utiliser des composants de fonction et des crochets. Par exemple, pour envoyer une action, vous utiliserez le useDispatch()
hook, qui vous permet d'appeler directement des actions, par exemple dispatch(todosActions.addTodo(text))
.
Le conteneur suivant a une entrée connectée à l'état React local, qui est utilisée pour ajouter une nouvelle tâche à l'état chaque fois qu'un bouton est cliqué:
import React, { useState } from 'react'; import './Home.css'; import { TodoList } from 'components' import { todosActions } from 'store/features/todos' import { useDispatch } from 'react-redux' function Home() { const dispatch = useDispatch(); const (text, setText) = useState(""); function handleClick() { dispatch(todosActions.addTodo(text)); setText(""); } function handleChange(e: React.ChangeEvent) { setText(e.target.value); } return ( ); } export default Home;
Ensuite, lorsque vous souhaitez utiliser les données stockées dans l'état, utilisez le useSelector
crochet. Cela prend une fonction qui sélectionne une partie de l'état à utiliser dans l'application. Dans ce cas, il définit le post
variable à la liste actuelle des tâches. Ceci est ensuite utilisé pour rendre un nouvel élément todo pour chaque entrée dans state.todos
.
import React from 'react'; import { useSelector } from 'store' import { Container, List, ListItem, Title } from './styles' function TodoList() { const posts = useSelector(state => state.todos) return (); } export default TodoList; {posts.map(({ id, title }) => (
))} {title} : {id}
Vous pouvez en fait créer des fonctions de sélection personnalisées pour gérer cela pour vous, enregistrées dans le /features/
dossier tout comme les actions et les réducteurs.
Une fois que vous avez tout configuré et compris, vous voudrez peut-être vous pencher sur la configuration de Redux Devtools, la configuration d'un middleware comme Redux Logger ou connected-react-router
, ou en installant un modèle à effets secondaires tel que Redux Sagas.