Fermer

mai 17, 2024

Guide du Redux moderne : Redux Toolkit et Redux Toolkit Query

Guide du Redux moderne : Redux Toolkit et Redux Toolkit Query


Apprenez à utiliser Redux de manière moderne en tirant parti de Redux Toolkit et de Redux Toolkit Query pour réduire le code passe-partout pour la gestion de l’état partagé et des données du serveur.

Pendant de nombreuses années, Redux a porté la couronne du choix de gestion d’état le plus populaire pour les applications React. Il offrait une approche prévisible pour gérer la gestion globale et partagée des états dans les applications modernes qui devenaient plus dynamiques et nécessitaient de meilleures façons de gérer les mises à jour d’état.

Malgré toutes ses lacunes et la montée en puissance de nouvelles solutions de gestion d’état, telles que Zustand ou Jotai, React-Redux, qui fournit les liaisons React officielles pour Redux, reste la bibliothèque de gestion d’état la plus téléchargée dans les applications React.

Cependant, ce n’est pas à cause de Redux lui-même, car les grandes applications deviennent généralement très complexes en raison de tous les passe-partout requis par Redux. Au lieu de cela, ce qui maintient Redux en vie, c’est la bibliothèque Redux Toolkit.

Dans cet article, nous expliquerons comment utiliser Redux de manière moderne en tirant parti de Redux Toolkit et de Redux Toolkit Query.

Qu’est-ce que Redux ?

Les concepts de base de Redux sont les suivants État, Actions et réducteurs. Redux conserve l’intégralité de l’état dans un seul objet, ce qui facilite le suivi et la gestion du flux de données et agit comme source de vérité pour l’état dans notre application.

Pour mettre à jour l’état, nous devons envoyer une action, qui est un simple objet JavaScript qui doit contenir un type propriété. Il est utilisé pour indiquer quel type d’action est effectué.

L’extrait ci-dessous est un exemple d’action qui doit incrémenter l’état de la valeur spécifiée. amount.

{
  type: 'INCREMENT',
  amount: 1
}

Enfin, les réducteurs décident comment l’état Redux doit changer en fonction de l’action envoyée. Les réducteurs sont de pures fonctions qui utilisent l’état actuel et l’action envoyée pour renvoyer un nouvel état.

Voici un exemple de simple counter réducteur :

function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + action.amount;
    case 'DECREMENT':
      return state - action.amount;
    default:
      return state;
  }
}

Quels problèmes Redux a résolus et ses avantages et inconvénients

Redux est très rapidement devenu la solution de gestion d’état la plus populaire pour les applications React. Les applications sont devenues plus faciles à maintenir en raison de la centralisation de l’état, s’appuyant sur des actions pour les mettre à jour et du fait que Redux a imposé l’immuabilité de l’état, ce qui a abouti à un comportement plus prévisible et à un débogage plus facile.

De plus, Redux DevTools a rendu le débogage d’état beaucoup plus facile, car ils offraient des fonctionnalités puissantes, telles que le débogage de voyage dans le temps, qui permettaient aux développeurs de suivre les changements d’état. Avec une communauté et un écosystème étendus, il était également facile d’améliorer les fonctionnalités de Redux en l’étendant avec des plugins middleware. Par exemple, conserver l’état de Redux est aussi simple que d’ajouter le middleware Redux Persist.

Cependant, travailler avec Redux n’a pas été que du soleil et des roses. L’une des plaintes les plus courantes concernant Redux est qu’il nécessite une grande quantité de code passe-partout, même pour un état simple.

Pour mettre plus de sel dans la plaie, Redux n’autorise même pas les opérations asynchrones et nécessite des bibliothèques supplémentaires, telles que Redux Thunk ou Redux Saga. Cela a provoqué une défragmentation de l’écosystème et a donné aux applications React différentes manières d’effectuer des opérations asynchrones, même si toutes utilisaient Redux.

De plus, de nombreux développeurs ont abusé de Redux et, au lieu de l’utiliser uniquement pour des états véritablement globaux ou partagés, ils l’ont utilisé pour toutes sortes d’états. Par conséquent, de nombreuses applications React étaient beaucoup plus complexes et remplies de code inutile qu’elles n’auraient dû l’être. Heureusement, une nouvelle façon d’utiliser Redux est désormais disponible : Redux Toolkit.

Redux Toolkit (RTK) est l’ensemble d’outils officiel, avisé et inclus avec des piles pour un développement Redux efficace. Il s’agit d’un wrapper autour de la fonctionnalité principale de Redux combinée à Redux Thunk et Immer fournissant des utilitaires pour simplifier les actions, les réducteurs et la création de magasins.

Mise en place d’un nouveau projet React

En ce qui concerne le code, la meilleure façon d’apprendre quelque chose est par la pratique, créons donc un nouveau projet React pour montrer la différence entre Redux pur et Redux Toolkit. Nous utiliserons Vite pour créer un projet React.

npm create vite@latest the-guide-to-modern-redux -- --template react

Ensuite, accédez au répertoire et installez les bibliothèques requises pour travailler avec Redux et Redux Toolkit.

cd the-guide-to-modern-redux
npm install redux redux-thunk react-redux @reduxjs/toolkit  
npm run dev

Le dev la commande démarrera le serveur Vite sur http://localhost:5173


Vous pouvez trouver l’exemple de code complet pour cet article dans ce dépôt GitHub. Ci-dessous, vous pouvez également trouver un exemple interactif de Stackblitz.

Redux avec Redux-Thunk

Commençons par créer une fonctionnalité pour récupérer et afficher une liste de publications en utilisant Pure Redux. Cela montrera bien la quantité de code passe-partout nécessaire pour gérer la récupération et le stockage de certaines données.

Tout d’abord, créons un magasin Redux, des actions et des réducteurs pour gérer la récupération et le stockage des publications dans un magasin Redux, ainsi que la modification d’un état de chargement pour indiquer qu’une requête API est effectuée.

src/components/redux/store/redux.store.js

import { createStore, combineReducers, applyMiddleware } from "redux";
import { postsReducer } from "./posts.reducer";
import thunk from "redux-thunk";

const appReducers = combineReducers({
  posts: postsReducer,
});

export const store = createStore(appReducers, applyMiddleware(thunk));

L’initialisation du magasin est assez simple. Le combineReducers La méthode est utilisée pour combiner plusieurs réducteurs, qui sont ensuite transmis au createStore méthode. Pour cet exemple, nous n’avons pas vraiment besoin combineReducers, mais il est couramment utilisé, car les applications n’ont que rarement un seul réducteur. De plus, nous appliquons le thunk middleware, car Redux en lui-même ne prend pas en charge les opérations asynchrones.

src/components/redux/store/posts.actions.js

export const POSTS_ACTIONS = {
  SET_IS_LOADING_FETCH_POSTS: "SET_IS_LOADING_FETCH_POSTS",
  FETCH_POSTS: "FETCH_POSTS",
  SET_POSTS: "SET_POSTS",
};

export const setIsLoadingFetchPosts = isLoading => {
  return {
    type: POSTS_ACTIONS.SET_IS_LOADING_FETCH_POSTS,
    payload: isLoading,
  };
};

export const setPostsAction = posts => {
  return {
    type: POSTS_ACTIONS.SET_POSTS,
    payload: posts,
  };
};

export const fetchPostsAction = () => {
  return async dispatch => {
    try {
      dispatch(setIsLoadingFetchPosts(true));
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/posts"
      );
      const data = await response.json();
      dispatch(setPostsAction(data.slice(0, 10)));
    } catch (error) {
    } finally {
      dispatch(setIsLoadingFetchPosts(false));
    }
  };
};

Dans le posts.actions.js fichier, nous avons :

  • constantes d’action – Valeurs utilisées par les créateurs et les réducteurs d’actions pour les actions type propriété.
  • créateurs d’action – Fonctions qui créent un objet d’action, qui est ensuite transmis au dispatch méthode.
  • remerciements/actions – Actions qui renvoient une fonction asynchrone, qui reçoit dispatch et getState méthodes comme arguments. Cette fonctionnalité est assurée par le Redux-Thunk bibliothèque. Dans le code ci-dessus, nous avons fetchPostsActionqui récupère une liste de publications et met à jour le posts et isLoadingPosts déclare en conséquence.

Ensuite, créons le réducteur de publications.

src/components/redux/store/posts.reducer.js

import { POSTS_ACTIONS } from "./posts.actions";

const initialState = {
  isLoadingPosts: false,
  posts: [],
};

export const postsReducer = (state = initialState, action) => {
  switch (action.type) {
    case POSTS_ACTIONS.SET_POSTS:
      return {
        ...state,
        posts: action.payload,
      };
    case POSTS_ACTIONS.SET_IS_LOADING_FETCH_POSTS:
      return {
        ...state,
        isLoadingPosts: action.payload,
      };
    default:
      return state;
  }
};

Le postsReducer est responsable du stockage des posts tableau et isLoadingPosts drapeau. Lorsque le réducteur est appelé, il renvoie un nouvel état basé sur l’action envoyée. Dans l’instruction switch, le action.type est vérifié pour déterminer si le réducteur doit renvoyer un nouvel état ou le précédent.

Maintenant, nous devons créer un composant qui distribuera l’action pour récupérer une liste de publications et les afficher.

src/components/redux/Posts.jsx

import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchPostsAction } from "./store/posts.actions";

const Posts = () => {
  const { posts, isLoadingPosts } = useSelector(state => state.posts);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchPostsAction());
  }, []);

  return (
    <div>
      <h1>Pure Redux Example</h1>
      {isLoadingPosts ? (
        <div>Loading...</div>
      ) : (
        <div>
          <ul
            style={{
              listStyle: "none",
            }}
          >
            {posts.map(post => {
              return <li key={post.id}>{post.title}</li>;
            })}
          </ul>
        </div>
      )}
    </div>
  );
};

export default Posts;

Enfin et surtout, nous devons rendre le Posts composant et enveloppez-le avec le Provider depuis react-redux.

src/App.jsx

import { Provider } from "react-redux";
import "./App.css";
import Posts from "./components/redux/Posts";
import { store as pureReduxStore } from "./components/redux/store/redux.store";

function App() {
  return (
    <>
      <Provider store={pureReduxStore}>
        <Posts />
      </Provider>
    </>
  );
}

export default App;

L’image ci-dessous montre à quoi devraient ressembler les publications.

Messages avec Redux pur

Afin de récupérer et d’afficher une liste de publications, nous avons dû créer plusieurs fichiers et beaucoup de code, et cet exemple n’a même pas de gestion des erreurs. Implémentons la même fonctionnalité en utilisant Redux Toolkit.

De la même manière que pour l’exemple Redux pur, nous devons configurer un magasin Redux. Cependant, cette fois, nous le faisons en utilisant le configureStore méthode de la @reduxjs/toolkit emballer.

src/components/redux-toolkit/store/rtk.store.js

import { configureStore } from "@reduxjs/toolkit";
import postsReducer from "./posts.slice";

export const store = configureStore({
  reducer: {
    posts: postsReducer,
  },
});

Lorsque nous travaillons avec Redux Toolkit, nous n’avons pas besoin de créer nous-mêmes des créateurs d’actions et des types d’actions. Au lieu de cela, nous pouvons utiliser le createSlice méthode, qui accepte les éléments suivants : l’état initial, l’objet des fonctions de réduction et le nom de la tranche. De plus, nous pouvons le combiner avec le createAsyncThunk méthode lorsque nous devons effectuer des opérations asynchrones.

src/components/redux-toolkit/store/posts.slice.js

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

export const fetchPosts = createAsyncThunk("posts/fetch", async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts");
  return response.json();
});

const initialState = {
  posts: [],
  isLoadingPosts: false,
};

const postsSlice = createSlice({
  name: "posts",
  initialState,
  reducers: {},
  extraReducers: builder => {
    builder.addCase(fetchPosts.pending, (state, action) => {
      return {
        ...state,
        isLoadingPosts: true,
      };
    });
    builder.addCase(fetchPosts.fulfilled, (state, action) => {
      return {
        ...state,
        posts: action.payload.slice(0, 10),
        isLoadingPosts: false,
      };
    });
  },
});

export default postsSlice.reducer;

Depuis le posts les données proviennent d’une API, nous ne définissons aucun réducteur. Au lieu de cela, nous ajoutons des réducteurs supplémentaires, basés sur l’état du fetchPosts je pense. Quand le fetchPosts la réflexion est en cours, le isLoadingPosts l’état est défini sur trueet quand il est rempli, les valeurs de isLoadingPosts et posts sont mis à jour en conséquence.

Enfin, créons un nouveau Posts composant.

src/components/redux-toolkit/Posts.jsx

import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchPosts } from "./store/posts.slice";

const Posts = () => {
  const { posts, isLoadingPosts } = useSelector(state => state.posts);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchPosts());
  }, []);

  return (
    <div>
      <h1>Redux Toolkit Example</h1>
      {isLoadingPosts ? (
        <div>Loading...</div>
      ) : (
        <div>
          <ul
            style={{
              listStyle: "none",
            }}
          >
            {posts.map(post => {
              return <li key={post.id}>{post.title}</li>;
            })}
          </ul>
        </div>
      )}
    </div>
  );
};

export default Posts;

Le Posts Le composant est presque exactement le même que celui de l’exemple Redux pur. Nous avons juste dû changer la méthode d’action appelée dans le useEffect. Au lieu de fetchPostsActionle fetchPosts méthode de la posts.slice.js le fichier est utilisé.

La dernière chose à faire est de mettre à jour le App composant.

src/App.jsx

import { Provider } from "react-redux";
import "./App.css";
import Posts from "./components/redux/Posts";
import RtkPosts from "./components/redux-toolkit/Posts";
import { store as pureReduxStore } from "./components/redux/store/redux.store";
import { store as rtkStore } from "./components/redux-toolkit/store/rtk.store";

function App() {
  return (
    <>
      <Provider store={pureReduxStore}>
        <Posts />
      </Provider>
      <Provider store={rtkStore}>
        <RtkPosts />
      </Provider>
    </>
  );
}

export default App;

Avec Redux Toolkit, nous avons dû écrire beaucoup moins de code qu’avec Redux pur pour obtenir la même fonctionnalité. Cependant, ce n’est pas tout, car Redux Toolkit propose également un module complémentaire appelé Redux Toolkit Query (RTK Query), qui peut être utilisé pour traiter les requêtes API et gérer l’état du serveur. Implémentons la fonctionnalité de publication à l’aide de RTK Query.

Gestion des requêtes API et de l’état du serveur avec la requête Redux Toolkit

Redux Toolkit Query est un outil puissant de récupération et de mise en cache de données, qui simplifie le travail avec les requêtes API. Premièrement, au lieu d’utiliser createSlice et createAsyncThunk méthodes, nous utiliserons createApi et fetchBaseQuery.

src/components/redux-toolkit-query/store/posts.api-slice.js

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

export const postsApiSlice = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: "https://jsonplaceholder.typicode.com",
  }),
  endpoints: builder => ({
    getPosts: builder.query({
      query: () => "posts",
    }),
  }),
});

export const { useGetPostsQuery } = postsApiSlice;

Nous avons maintenant beaucoup moins de code que dans l’exemple précédent. Nous fournissons les résultats de fetchBaseQuery méthode à la baseQuery option. fetchBaseQuery est un wrapper au-dessus du natif fetch. Ensuite, nous configurons les points de terminaison. Dans ce cas, nous configurons un seul point de terminaison pour récupérer les publications.

Outre l’exportation du postsApiSlicenous exportons également le useGetPostsQuery, qui est un hook généré automatiquement. Redux Toolkit génère automatiquement des hooks pour tous les points de terminaison.

Ensuite, nous devons configurer un nouveau magasin. La configuration est un peu différente de celle d’avec Redux Toolkit uniquement. Nous devons configurer le réducteur généré par la tranche API, ajouter un middleware et configurer les écouteurs.

src/components/redux-toolkit-query/store/rtk-query.store.js

import { configureStore } from "@reduxjs/toolkit";
import { postsApiSlice } from "./posts.api-slice";
import { setupListeners } from "@reduxjs/toolkit/query";

export const store = configureStore({
  reducer: {
    [postsApiSlice.reducerPath]: postsApiSlice.reducer,
  },
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware().concat(postsApiSlice.middleware),
});

setupListeners(store.dispatch);

Ensuite, créons un nouveau Postscomposant qui utilisera le useGetPostsQuery crochet.

src/components/redux-toolkit-query/Posts.jsx

import { useGetPostsQuery } from "./store/posts.api-slice";

const Posts = () => {
  const { isLoading: isLoadingPosts, data: posts = [] } = useGetPostsQuery();

  return (
    <div>
      <h1>Redux Toolkit Query Example</h1>
      {isLoadingPosts ? (
        <div>Loading...</div>
      ) : (
        <div>
          <ul
            style={{
              listStyle: "none",
            }}
          >
            {posts.map(post => {
              return <li key={post.id}>{post.title}</li>;
            })}
          </ul>
        </div>
      )}
    </div>
  );
};

export default Posts;

Avec Redux Toolkit Query, nous avons pu supprimer du code, car nous n’avons pas besoin de compter sur useEffect, useSelector et useDispatch. Les hooks de tranche d’API gèrent tout cela, et nous n’avons pas besoin de distribuer des actions ou de gérer manuellement les états de chargement.

Enfin et surtout, rendons le nouveau Posts composant dans le App.jsx déposer.

src/App.jsx

import { Provider } from "react-redux";
import "./App.css";
import Posts from "./components/redux/Posts";
import RtkPosts from "./components/redux-toolkit/Posts";
import RtkQueryPosts from "./components/redux-toolkit-query/Posts";
import { store as pureReduxStore } from "./components/redux/store/redux.store";
import { store as rtkStore } from "./components/redux-toolkit/store/rtk.store";
import { store as rtkQueryStore } from "./components/redux-toolkit-query/store/rtk.store";

function App() {
  return (
    <>
      <Provider store={pureReduxStore}>
        <Posts />
      </Provider>
      <Provider store={rtkStore}>
        <RtkPosts />
      </Provider>
      <Provider store={rtkQueryStore}>
        <RtkQueryPosts />
      </Provider>
    </>
  );
}

export default App;

Conclusion

L’intégration de Redux Toolkit avec Redux Toolkit Query dans un projet peut réduire considérablement la quantité de code passe-partout requis pour gérer l’état partagé et les données du serveur.

RTK Query simplifie non seulement la gestion des requêtes API, mais fournit également une mise en cache, une invalidation, un garbage collection automatisés et bien plus encore. Par conséquent, si vous démarrez un nouveau projet et souhaitez utiliser Redux, assurez-vous d’utiliser Redux Toolkit pour vous éviter d’écrire beaucoup de code inutile.

Nous n’avons couvert qu’un petit sous-ensemble de fonctionnalités offertes par Redux Toolkit, alors assurez-vous de consulter son documents.




Source link