Redux est un conteneur d'état prévisible pour les applications JavaScript. C'est une bibliothèque populaire pour gérer l'état dans les applications React. Il peut offrir une meilleure expérience aux développeurs lors de son utilisation avec TypeScript, qui est un sur-ensemble de JavaScript qui vérifie le code pour le rendre robuste et compréhensible.
Dans ce guide, je vais vous montrer comment utiliser Redux dans votre projet React TypeScript en créant une application qui permet d'ajouter, d'afficher et de supprimer des articles.
Sorry for the interrupt!
If you're interested in learning TypeScript, React or Redux in a comprehensive way, I highly recommend these bestseller courses:
> Understanding TypeScript - 2020 Edition
> React - The Complete Guide (incl Hooks, React Router, Redux)
- Conditions préalables
- Configuration
- Créer les types
- Créer les types d'actions
- Créer les créateurs d'action
- Créer un réducteur
- Créer un store
- Créer les composants
Conditions préalables
Ce didacticiel suppose que vous avez au moins une compréhension de base de React, Redux et TypeScript. Donc, si vous n'êtes pas familier avec ces technologies, essayez d'abord de lire ce guide pratique de TypeScript ou ce tutoriel React Redux - sinon, commençons.
Configuration
Pour utiliser Redux et TypeScript, nous devons créer une nouvelle application React.
Pour ce faire, ouvrons la CLI (interface de ligne de commande) et exécutons cette commande:
npx create-react-app my-app --template typescript
Ensuite, structurons le projet comme suit:
├── src
| ├── components
| | ├── AddArticle.tsx
| | └── Article.tsx
| ├── store
| | ├── actionCreators.ts
| | ├── actionTypes.ts
| | └── reducer.ts
| ├── type.d.ts
| ├── App.test.tsx
| ├── App.tsx
| ├── index.css
| ├── index.tsx
| ├── react-app-env.d.ts
| └── setupTests.ts
├── tsconfig.json
├── package.json
└── yarn.lock
La structure des fichiers du projet est assez simple. Cependant, il y a deux choses à noter:
- Le dossier
store
qui contient les fichiers liés à React Redux. - Le fichier
type.d.ts
qui contient les types TypeScript, qui peut maintenant être utilisé dans d'autres fichiers sans importation.
Cela étant dit, nous pouvons maintenant installer Redux et créer notre tout premier store.
Alors, ouvrons le projet et exécutons la commande suivante:
yarn add redux react-redux redux-thunk
Ou lors de l'utilisation de npm
npm install redux react-redux redux-thunk
Nous devons également installer leurs types en tant que dépendances de développement pour aider TypeScript à comprendre les bibliothèques.
Alors, exécutons à nouveau cette commande sur la CLI.
yarn add -D @types/redux @types/react-redux @types/redux-thunk
Ou pour npm
npm install -D @types/redux @types/react-redux @types/redux-thunk
Génial! Avec cette avancée, nous pouvons maintenant créer les types TypeScript pour le projet dans la section suivante.
Créer les types
TypeScript Types vous permet de définir des types pour vos variables, paramètres de fonction, etc.
- type.d.ts
interface IArticle {
id: number
title: string
body: string
}
type ArticleState = {
articles: IArticle[]
}
type ArticleAction = {
type: string
article: IArticle
}
type DispatchType = (args: ArticleAction) => ArticleAction
Ici, nous commençons par déclarer l'interface IArticle
, qui reflète la forme d'un article donné. Ensuite, nous avons ArticleState
, ArticleAction
et DispatchType
qui seront utilisés comme types respectivement pour l'objet d'état, les créateurs d'action et la fonction de répartition fournie par Redux.
Cela dit, nous avons maintenant les types nécessaires pour plonger dans React Redux. Créons les types d'actions.
Créer les types d'actions
- store/actionTypes.ts
export const ADD_ARTICLE = "ADD_ARTICLE"
export const REMOVE_ARTICLE = "REMOVE_ARTICLE"
Nous avons besoin de deux types d'action pour le store Redux. Un pour ajouter des articles et un autre pour supprimer.
Créer les créateurs d'action
- store/actionCreators.ts
import * as actionTypes from "./actionTypes"
export function addArticle(article: IArticle) {
const action: ArticleAction = {
type: actionTypes.ADD_ARTICLE,
article,
}
return simulateHttpRequest(action)
}
export function removeArticle(article: IArticle) {
const action: ArticleAction = {
type: actionTypes.REMOVE_ARTICLE,
article,
}
return simulateHttpRequest(action)
}
export function simulateHttpRequest(action: ArticleAction) {
return (dispatch: DispatchType) => {
setTimeout(() => {
dispatch(action)
}, 500)
}
}
Dans ce tutoriel, je simulerai la requête HTTP en la retardant de 0,5 seconde. Mais n'hésitez pas à utiliser un vrai serveur si vous le souhaitez aussi.
Ici, la fonction addArticle
enverra une action pour ajouter un nouvel article et la méthode removeArticle
fera le contraire et supprimera donc l'article passé en argument.
Créer un réducteur
Un réducteur est une fonction pure qui reçoit l'état du store et une action en tant que paramètres, puis renvoie l'état mis à jour.
- store/reducer.ts
import * as actionTypes from "./actionTypes"
const initialState: ArticleState = {
articles: [
{
id: 1,
title: "post 1",
body:
"Quisque cursus, metus vitae pharetra Nam libero tempore, cum soluta nobis est eligendi",
},
{
id: 2,
title: "post 2",
body:
"Harum quidem rerum facilis est et expedita distinctio quas molestias excepturi sint",
},
],
}
Comme vous pouvez le voir ici, nous déclarons un état initial afin d'avoir des articles à afficher lors du chargement de la page. L'objet state doit correspondre au type ArticleState
, sinon TypeScript générera une erreur.
- store/reducer.ts
const reducer = (
state: ArticleState = initialState,
action: ArticleAction
): ArticleState => {
switch (action.type) {
case actionTypes.ADD_ARTICLE:
const newArticle: IArticle = {
id: Math.random(), // not really unique
title: action.article.title,
body: action.article.body,
}
return {
...state,
articles: state.articles.concat(newArticle),
}
case actionTypes.REMOVE_ARTICLE:
const updatedArticles: IArticle[] = state.articles.filter(
article => article.id !== action.article.id
)
return {
...state,
articles: updatedArticles,
}
}
return state
}
export default reducer
Ensuite, nous avons la fonction reducer
qui attend l'état précédent et une action pour pouvoir mettre à jour le store. Ici, nous avons deux actions: une pour ajouter et une autre pour supprimer.
Avec cela en place, nous pouvons maintenant gérer l'état avec le réducteur. Créons maintenant un store pour le projet.
Créer un store
Un store Redux est l'endroit où vit l'état de votre application.
- index.tsx
import * as React from "react"
import { render } from "react-dom"
import { createStore, applyMiddleware, Store } from "redux"
import { Provider } from "react-redux"
import thunk from "redux-thunk"
import App from "./App"
import reducer from "./store/reducer"
const store: Store<ArticleState, ArticleAction> & {
dispatch: DispatchType
} = createStore(reducer, applyMiddleware(thunk))
const rootElement = document.getElementById("root")
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)
Comme vous pouvez le voir, nous importons la fonction réductrice puis la passons en argument à la méthode createStore
afin de créer un nouveau store Redux. Le middleware redux-thunk
doit également être utilisé comme deuxième paramètre de la méthode pour pouvoir gérer le code asynchrone.
Ensuite, nous connectons React à Redux en fournissant l'objet store
comme props au composant Provider
.
Nous pouvons maintenant utiliser Redux dans ce projet et accéder au magasin. Alors, créons les composants pour obtenir et manipuler les données.
Créer les composants
- components/AddArticle.tsx
import * as React from "react"
type Props = {
saveArticle: (article: IArticle | any) => void
}
export const AddArticle: React.FC<Props> = ({ saveArticle }) => {
const [article, setArticle] = React.useState<IArticle | {}>()
const handleArticleData = (e: React.FormEvent<HTMLInputElement>) => {
setArticle({
...article,
[e.currentTarget.id]: e.currentTarget.value,
})
}
const addNewArticle = (e: React.FormEvent) => {
e.preventDefault()
saveArticle(article)
}
return (
<form onSubmit={addNewArticle} className="Add-article">
<input
type="text"
id="title"
placeholder="Title"
onChange={handleArticleData}
/>
<input
type="text"
id="body"
placeholder="Description"
onChange={handleArticleData}
/>
<button disabled={article === undefined ? true : false}>
Add article
</button>
</form>
)
}
Pour ajouter un nouvel article, nous utiliserons ce composant de formulaire. Il reçoit la fonction saveArticle
comme paramètre, ce qui permet d'ajouter de nouveaux articles au store. L'objet article doit suivre le type IArticle
pour rendre TypeScript heureux.
- components/Article.tsx
import * as React from "react"
import { Dispatch } from "redux"
import { useDispatch } from "react-redux"
type Props = {
article: IArticle
removeArticle: (article: IArticle) => void
}
export const Article: React.FC<Props> = ({ article, removeArticle }) => {
const dispatch: Dispatch<any> = useDispatch()
const deleteArticle = React.useCallback(
(article: IArticle) => dispatch(removeArticle(article)),
[dispatch, removeArticle]
)
return (
<div className="Article">
<div>
<h1>{article.title}</h1>
<p>{article.body}</p>
</div>
<button onClick={() => deleteArticle(article)}>Delete</button>
</div>
)
}
Le composant Article
affiche un objet article.
La fonction removeArticle
doit être distribuée pour accéder au store et donc supprimer un article donné. C'est la raison pour laquelle nous utilisons ici le hook useDispatch
fourni par Redux pour terminer l'action de suppression.
Ensuite, l'utilisation de useCallback
permet d'éviter un nouveau rendu inutile en mémorisant les valeurs comme dépendances.
Nous avons enfin les composants nécessaires pour ajouter et afficher les articles. Ajoutons maintenant la dernière pièce au puzzle en les utilisant dans le fichier App.tsx
.
- App.tsx
import * as React from "react"
import { useSelector, shallowEqual, useDispatch } from "react-redux"
import "./styles.css"
import { Article } from "./components/Article"
import { AddArticle } from "./components/AddArticle"
import { addArticle, removeArticle } from "./store/actionCreators"
import { Dispatch } from "redux"
const App: React.FC = () => {
const articles: readonly IArticle[] = useSelector(
(state: ArticleState) => state.articles,
shallowEqual
)
const dispatch: Dispatch<any> = useDispatch()
const saveArticle = React.useCallback(
(article: IArticle) => dispatch(addArticle(article)),
[dispatch]
)
return (
<main>
<h1>My Articles</h1>
<AddArticle saveArticle={saveArticle} />
{articles.map((article: IArticle) => (
<Article
key={article.id}
article={article}
removeArticle={removeArticle}
/>
))}
</main>
)
}
export default App
Le hook useSelector
permet d'accéder à l'état du store. Ici, nous passons shallowEqual
comme deuxième argument au hook pour dire à Redux d'utiliser l'égalité superficielle lors de la vérification des changements.
Ensuite, nous nous appuyons sur useDispatch
pour envoyer une action d'ajout d'articles dans le store. Enfin, nous parcourons le tableau des articles et passons chacun au composant Article
afin de l'afficher.
Cela étant fait, nous pouvons maintenant accéder à la racine du projet et exécuter cette commande pour prévisualiser l'application dans le navigateur.
yarn start
Ou pour npm
npm start
Si vous ouvrez http://localhost:3000/
dans le navigateur, vous devriez voir ceci:
Génial! Notre application a l'air bien. Avec cela, nous avons maintenant terminé d'utiliser Redux dans une application React TypeScript.
Vous pouvez trouver le projet fini dans ce CodeSandbox
Merci d'avoir lu.