7 étapes pour comprendre React Redux

Dec 26, 2019☕ ☕ 15 min Follow me on Twitter

Subscribe to receive the free weekly article

React est génial, on ne peut pas le dire assez. Mais en ce qui concerne la partie gestion de l'État, les choses deviennent délicates. Il y a tellement de terminologie à retenir: état, store, actions, réducteurs, middleware, etc. Avec des applications de taille moyenne ou plus grandes, la gestion de notre état peut être très difficile à mesure que notre application se développe. Nous devons le gérer soit par redux, soit par des alternatives comme le context API, flux, etc. Dans cet article, nous allons nous concentrer sur redux et son fonctionnement avec react. Redux est une bibliothèque autonome, elle est indépendante du framework, c'est-à-dire que vous pouvez l'utiliser avec d'autres frameworks ou simplement avec du JavaScript pur.

Dans cet article, je vais vous guider à travers 7 étapes pour comprendre react redux de la manière la plus simple.

Preréquis

Ce post suppose que vous avez au moins une compréhension de base ou intermédiaire de React et ES6. Ensuite, vous devrez créer une nouvelle application React avec cette commande:

npx create-react-app react-redux-example

Et ajoutez à votre application React les packages redux et react-redux en exécutant sur votre shell

npm install redux react-redux

Ensuite, nous devons créer quelques fichiers.

  • Ajoutez un dossier containers dans le src, puis créez le fichier Articles.js.
import React, { useState } from "react"
import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"

const Articles = () => {
  const [articles, setArticles] = useState([
    { id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
    { id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
  ])
  const saveArticle = e => {
    e.preventDefault()
    // the logic will be updated later
  }

  return (
    <div>
      <AddArticle saveArticle={saveArticle} />
      {articles.map(article => (
        <Article key={article.id} article={article} />
      ))}
    </div>
  )
}

export default Articles
  • Ajoutez un dossier components dans lesrc, puis créez AddArticle/AddArticle.js et Article/Article.js.
  • Dans Article.js
import React from "react"
import "./Article.css"

const article = ({ article }) => (
  <div className="article">
    <h1>{article.title}</h1>
    <p>{article.body}</p>
  </div>
)

export default article
  • Dans AddArticle.js
import React, { useState } from "react"
import "./AddArticle.css"

const AddArticle = ({ saveArticle }) => {
  const [article, setArticle] = useState()

  const handleArticleData = e => {
    setArticle({
      ...article,
      [e.target.id]: e.target.value,
    })
  }
  const addNewArticle = e => {
    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="Body"
        onChange={handleArticleData}
      />
      <button>Add article</button>
    </form>
  )
}
export default AddArticle
  • Dans App.js
import React from "react"
import Articles from "./containers/Articles"

function App() {
  return <Articles />
}
export default App

Si vous avez fini avec la condition préalable, nous pouvons passer à autre chose et démystifier ce qu'est un état.

1. Qu'est-ce qu'un état?

L'état est le cœur de chaque composant à état réactif. Il détermine comment le composant doit se comporter ou être rendu. Pour vraiment comprendre l'état, nous devons l'appliquer à de vrais exemples. L'utilisateur est-il authentifié? est un état qui contrôle si un utilisateur est authentifié ou non, le modal est-il ouvert? est aussi un état qui regarde si un modal donné est ouvert ou pas même chose pour le cas d'une liste d'articles ou d'un compteur etc.

// Class based component
state = {
  articles: [
    { id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
    { id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
  ],
}
// React hooks
const [articles, setArticles] = useState([
  { id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
  { id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
])

Maintenant que nous savons ce qu'est un état, il est temps d'introduire redux et de plonger plus profondément dedans.

2. C'est quoi redux et pourquoi en avons-nous besoin?

Gérer notre état sans redux ou alternatives peut être difficile. Imaginez que nous devions vérifier sur chaque composant si l'utilisateur est authentifié ou non. Pour gérer ce cas d'utilisation, nous devons passer des props à travers chaque composant et suite à la croissance de l'application, il est tout simplement impossible de gérer notre état de cette manière. Et c'est là que redux entre en scéne.

Redux est une bibliothèque indépendante qui nous aide à gérer notre état en donnant à nos composants l'état dont il a besoin via un store central. Redux stocke tout l'état de notre application dans une arborescence d'objets immuable.

Un autre terme générique: store, pour bien le comprendre, il faut d'abord expliquer ce qu'est un réducteur?

3. Qu'est-ce qu'un réducteur?

Un réducteur est une fonction pure qui reçoit l'ancien état (précédent) et une action comme arguments, puis renvoie en sortie l'état mis à jour. Le réducteur ne gère que le code synchrone, ce qui signifie aucun effet secondaire comme une requête HTTP ou quelque chose comme ça. Nous pouvons toujours gérer du code asynchrone avec redux et nous apprendrons comment le faire plus tard. Au passage, si vous êtes confus par le terme action, pas de soucis, ce sera beaucoup plus clair plus tard. Créons d'abord notre tout premier réducteur.

La structure de vos fichiers dépend entièrement de vous, cependant je vais suivre la convention et créer un dossier store dans le projet pour contenir nos réducteurs, actions etc. Ensuite, créez un fichier reducer.js.

  • Dans reducer.js
const initialState = {
  articles: [
    { id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
    { id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
  ],
}

const reducer = (state = initialState, action) => {
  return state
}
export default reducer

Comme je l'ai dit plus tôt, un réducteur est juste une fonction qui reçoit l'état précédent et une action en tant que paramètres et renvoie l'état mis à jour. Ici, nous n'avons pas d'état antérieur, il ne sera donc pas défini, nous devons donc l'initialiser avec initialState qui contient nos articles prédéfinis.

Maintenant que nous avons installé notre réducteur, il est temps de créer notre store

4. Qu'est-ce qu'un store?

Un magasin contient tout l'arbre d'état de notre application React. C'est là que notre état d'application vit. Vous pouvez le voir comme un gros objet JavaScript. Pour créer un store, nous avons besoin d'un réducteur à passer en argument. Nous avons déjà un réducteur, connectons-le à notre store.

  • Dans index.js.
import React from "react"
import ReactDOM from "react-dom"
import { createStore } from "redux"

import "./index.css"
import App from "./App"
import reducer from "./store/reducer"

const store = createStore(reducer)

ReactDOM.render(<App />, document.getElementById("root"))

Pour créer un store, nous devons d'abord importer createStore à partir du paquet redux, puis importer notre réducteur et enfin le passer en argument au store createStore(reducer). Avec cela, nous avons réussi à créer notre store, mais nous n'avons pas encore fini, nous devons le connecter à notre application React.

5. Comment connecter notre store à React?

Pour connecter le store à React, nous devons importer une fonction d'assistance nommée Provider à partir du package react-redux. Ensuite, on enveloppe notre composant App avec Provider et on passe comme props le store qui a pour valeur notre store actuel.

  • Dans index.js.
import React from "react"
import ReactDOM from "react-dom"
import { createStore } from "redux"
import { Provider } from "react-redux"

import "./index.css"
import App from "./App"
import reducer from "./store/reducer"

const store = createStore(reducer)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
)

Ensuite, nous devons connecter notre composant au store redux.

  • In Articles.js
import React from "react"
import { connect } from "react-redux"

import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"

const Articles = ({ articles }) => {
  const saveArticle = e => {
    e.preventDefault()
    // the logic will be updated later
  }
  return (
    <div>
      <AddArticle saveArticle={saveArticle} />
      {articles.map(article => (
        <Article key={article.id} article={article} />
      ))}
    </div>
  )
}

const mapStateToProps = state => {
  return {
    articles: state.articles,
  }
}

export default connect(mapStateToProps)(Articles)

Ici, nous importons d'abord connect(), une fonction qui retourne une fonction d'ordre supérieur et reçoit en entrée un composant. Il nous aide à connecter notre composant au store et à nous donner accès à l'état.

Ensuite, nous déclarons une nouvelle fonction nommée mapStateToProps() (vous pouvez la nommer comme bon vous semble). Il est utilisé pour obtenir notre état du store redux. La fonction reçoit en paramètre state stocké dans redux et retourne un objet JavaScript qui contiendra nos articles.
Et pour atteindre le magasin, nous devons passer mapStateToProps() à la fonction connect(). Il prendra notre composant Articles et retournera un composant conteneur avec les props qu'il doit injecter. Cela signifie que nous pouvons maintenant obtenir notre état contenu dans le store. L'état est reçu par le composant via des props, nous pouvons toujours afficher les articles comme avant mais maintenant via redux.

Nous avons réussi à connecter notre store à React. Passons maintenant aux actions

6. Qu'est-ce qu'une action?

Une action est un paquet utile d'informations qui contient un type comme REMOVE_ARTICLE ou ADD_ARTICLE etc. Les actions sont distribuées à partir de votre composant. Il envoie les données de votre composant React à votre store Redux. L'action n'atteint pas le store, c'est juste le messager. Le store est changé par le réducteur.

Pour créer une action dans notre projet, nous devons créer dans notre dossier store un nouveau fichier nommé actionTypes.js.

export const ADD_ARTICLE = "ADD_ARTICLE"

Ensuite, nous avons besoin d'aller sur Articles.js et y ajouter ce code suivant.

import React from "react"
import { connect } from "react-redux"

import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"
import * as actionTypes from "../store/actionTypes"

const Articles = ({ articles, saveArticle }) => (
  <div>
    <AddArticle saveArticle={saveArticle} />
    {articles.map(article => (
      <Article key={article.id} article={article} />
    ))}
  </div>
)

const mapStateToProps = state => {
  return {
    articles: state.articles,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    saveArticle: article =>
      dispatch({ type: actionTypes.ADD_ARTICLE, articleData: { article } }),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Articles)

Ensuite, nous devons tout importer de actionTypes.js. Et créez une nouvelle fonction mapDispatchToProps qui reçoit une fonction dispatch en paramètre. Le mapDispatchToProps retourne un objet qui a une propriété saveArticle. C'est une référence à une fonction qui enverra une action dans notre store.

saveArticle contient une fonction anonyme qui reçoit notre article en argument et retourne la fonction dispatch. Il reçoit le type et les données à mettre à jour en tant que paramètres. Et comme vous le devinez, cela enverra l'action dans notre store.
Enfin, nous devons passer mapDispatchToProps comme deuxième argument à la fonction connect. Et pour le faire fonctionner, nous devons mettre à jour notre réducteur et ajouter l'action ADD_ARTICLE.

  • Dans store/reducer.js
import * as actionTypes from "./actionTypes"

const initialState = {
  articles: [
    { id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
    { id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
  ],
}

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case actionTypes.ADD_ARTICLE:
      const newArticle = {
        id: Math.random(), // not really unique but it's just an example
        title: action.article.title,
        body: action.article.body,
      }
      return {
        ...state,
        articles: state.articles.concat(newArticle),
      }
  }
  return state
}
export default reducer

Comme vous pouvez le voir, nous importons en premier actionTypes. Ensuite, nous vérifions dans notre fonction reducer si le type de l'action est égal à ADD_ARTICLE. Si c'est le cas, on crée d'abord un nouvel objet qui contient notre article, puis on l'ajoute à notre tableau d'articles. Avant de retourner l'état, nous copions l'ancien état, puis on utilise concat avec le nouvel article pour l'ajouter. De cette façon, nous gardons notre état sûr et immuable.

7. Comment gérer le code asynchrone avec redux?

Le réducteur comme je l'ai dis plus tôt ne gère que le code synchrone. Pour exécuter du code asynchrone, nous devons utiliser un créateur d'action. C'est une fonction qui renvoie une fonction ou une action devrais-je dire. Donc, pour l'utiliser dans notre projet, nous devons créer un nouveau fichier actionCreators.js.

  • Dans store/actionCreators.js
import * as actionTypes from "./actionTypes"

export const addArticle = article => {
  return {
    type: actionTypes.ADD_ARTICLE,
    article,
  }
}

Ici, nous déclarons un nouveau créateur d'action nommé addArticle. C'est une fonction qui reçoit article comme argument et renvoie le type de l'action et la valeur. Au fait, article est identique à article: article, c'est juste une syntaxe pratique ES6. Nous pouvons maintenant passer à autre chose et changer la fonction mapDispatchToProps dans le fichier Articles.js.

  • Dans Articles.js
import React from "react"
import { connect } from "react-redux"

import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"
import { addArticle } from "../store/actionCreators"

const Articles = ({ articles, saveArticle }) => (
  <div>
    <AddArticle saveArticle={saveArticle} />
    {articles.map(article => (
      <Article key={article.id} article={article} />
    ))}
  </div>
)

const mapStateToProps = state => {
  return {
    articles: state.articles,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    saveArticle: article => dispatch(addArticle(article)),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Articles)

Comme vous pouvez le voir, nous importons d'abord notre créateur d'action addArticle, puis dans la fonction mapDispatchToProps, nous mettons à jour l'argument passé à dispatch. Maintenant, il reçoit le créateur de l'action et sa valeur article.

Mais nous n'avons pas encore fini, nous devons ajouter un nouveau paquet redux-thunk à notre projet pour pouvoir gérer le code asynchrone.

npm install react-redux

redux-thunk est un middleware qui nous aidera à gérer le code asynchrone. Un middleware permet d'interagir avec les actions qui ont été envoyées au magasin avant qu'elles n'atteignent le réducteur. Maintenant, implémentons-le dans notre projet.

  • Dans index.js
import React from "react"
import ReactDOM from "react-dom"
import { createStore, applyMiddleware } from "redux"
import { Provider } from "react-redux"
import thunk from "redux-thunk"

import "./index.css"
import App from "./App"
import reducer from "./store/reducer"

const store = createStore(reducer, applyMiddleware(thunk))

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
)

Dans ce bloc de code, nous importons d'abord applyMiddleware de redux et thunk de redux-thunk. Ensuite, pour le faire fonctionner, nous devons passer à createStore un deuxième argument ou amplificateur qui reçoit notre middleware thunk. Ainsi, nous sommes désormais en mesure de gérer le code asynchrone. Mettons maintenant à jour notre créateur d'action.

  • Dans store/actionCreators.js
import * as actionTypes from "./actionTypes"

export const addArticle = article => {
  return {
    type: actionTypes.ADD_ARTICLE,
    article,
  }
}

export const simulateHttpRequest = article => {
  return dispatch => {
    setTimeout(() => {
      dispatch(addArticle(article))
    }, 3000)
  }
}

Pour cet article, nous allons simplement simuler une requête HTTP.

Ici, nous avons un nouveau créateur d'action simulateHttpRequest qui reçoit l'article en entrée et renvoie une fonction. En raison du middleware thunk, nous pouvons accéder à dispatch car notre middleware s'exécute entre l'envoi de notre action et le moment où l'action atteint le réducteur. Par conséquent, nous pouvons obtenir dispatch comme argument. Ensuite, on attende 3 secondes avec setTimeout pour simuler simplement une requête HTTP avant d'envoyer l'action et ajoutez l'article à notre tableau d'articles.

Nous avons un peu changé nos créateurs d'actions, pour le faire fonctionner à nouveau, nous devons mettre à jour Articles.js.

  • Dans Articles.js
import React from "react"
import { connect } from "react-redux"

import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"
import { simulateHttpRequest } from "../store/actionCreators"

const Articles = ({ articles, saveArticle }) => (
  <div>
    <AddArticle saveArticle={saveArticle} />
    {articles.map(article => (
      <Article key={article.id} article={article} />
    ))}
  </div>
)

const mapStateToProps = state => {
  return {
    articles: state.articles,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    saveArticle: article => dispatch(simulateHttpRequest(article)),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Articles)

Ici, la seule chose que nous devons faire est de changer addArticle en simulateHttpRequest, de cette façon, tout devrait fonctionner à nouveau, et maintenant, nous sommes capables de gérer du code asynchrone via redux.

Vous trouverer le projet final ici

Conclusion

Lorsqu'il s'agit de gérer des applications de taille moyenne à une taille plus grande, la gestion de notre état peut être très difficile. Et un paquet comme redux peut le rendre très facile. Il existe également des alternatives comme le context API(+hooks) qui est très utile et ne nécessite pas de bibliothèque tierce, mais apprendre comment utiliser redux est toujours pertinent.

Cependant, redux est inutile pour une application React simple comme notre projet, nous n'avons pas besoin de redux pour gérer notre état, mais il est plus facile de comprendre comment redux fonctionne avec une application très simple.

Ressources

Documentation officielle React Redux
Devtools Redux
Les bonnes pratiques de Redux
Redux saga
Le Context API

#react#redux

Support my work

Get articles in your inbox