Comment utiliser Redux dans votre application React TypeScript

Sep 08, 2020☕☕ 10 min Follow me on Twitter

Subscribe to receive the free weekly article

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.

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:

aperçu de l'application

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.

#typescript#react#redux

Support my work

Get articles in your inbox