Redux - Guide rapide

Redux est un conteneur d'état prévisible pour les applications JavaScript. À mesure que l'application se développe, il devient difficile de la garder organisée et de maintenir le flux de données. Redux résout ce problème en gérant l'état de l'application avec un seul objet global appelé Store. Les principes fondamentaux de Redux aident à maintenir la cohérence dans toute votre application, ce qui facilite le débogage et les tests.

Plus important encore, il vous offre une édition de code en direct combinée à un débogueur temporel. Il est flexible pour aller avec n'importe quelle couche de vue telle que React, Angular, Vue, etc.

Principes de Redux

La prévisibilité de Redux est déterminée par les trois principes les plus importants indiqués ci-dessous -

Source unique de vérité

L'état de l'ensemble de votre application est stocké dans une arborescence d'objets dans un seul magasin. L'état complet de l'application étant stocké dans une seule arborescence, cela facilite le débogage et accélère le développement.

L'état est en lecture seule

La seule façon de changer l'état est d'émettre une action, un objet décrivant ce qui s'est passé. Cela signifie que personne ne peut modifier directement l'état de votre application.

Les modifications sont effectuées avec des fonctions pures

Pour spécifier comment l'arborescence d'états est transformée par les actions, vous écrivez des réducteurs purs. Un réducteur est un endroit central où la modification d'état a lieu. Le réducteur est une fonction qui prend l'état et l'action en tant qu'arguments et renvoie un état nouvellement mis à jour.

Avant d'installer Redux, we have to install Nodejs and NPM. Voici les instructions qui vous aideront à l'installer. Vous pouvez ignorer ces étapes si vous avez déjà installé Nodejs et NPM sur votre appareil.

  • Visite https://nodejs.org/ et installez le fichier de package.

  • Exécutez le programme d'installation, suivez les instructions et acceptez le contrat de licence.

  • Redémarrez votre appareil pour l'exécuter.

  • Vous pouvez vérifier la réussite de l'installation en ouvrant l'invite de commande et en saisissant node -v. Cela vous montrera la dernière version de Node dans votre système.

  • Pour vérifier si npm est installé avec succès, vous pouvez taper npm –v qui vous renvoie la dernière version de npm.

Pour installer redux, vous pouvez suivre les étapes ci-dessous -

Exécutez la commande suivante dans votre invite de commande pour installer Redux.

npm install --save redux

Pour utiliser Redux avec l'application react, vous devez installer une dépendance supplémentaire comme suit -

npm install --save react-redux

Pour installer les outils de développement pour Redux, vous devez installer les éléments suivants en tant que dépendance -

Exécutez la commande ci-dessous dans votre invite de commande pour installer Redux dev-tools.

npm install --save-dev redux-devtools

Si vous ne souhaitez pas installer les outils de développement Redux et les intégrer dans votre projet, vous pouvez installer Redux DevTools Extension pour Chrome et Firefox.

Supposons que l'état de notre application soit décrit par un objet simple appelé initialState qui est comme suit -

const initialState = {
   isLoading: false,
   items: [],
   hasError: false
};

Chaque morceau de code de votre application ne peut pas changer cet état. Pour modifier l'état, vous devez envoyer une action.

Qu'est-ce qu'une action?

Une action est un objet simple qui décrit l'intention de provoquer un changement avec une propriété de type. Il doit avoir une propriété type qui indique le type d'action en cours d'exécution. La commande d'action est la suivante -

return {
   type: 'ITEMS_REQUEST', //action type
   isLoading: true //payload information
}

Les actions et les états sont maintenus ensemble par une fonction appelée Réducteur. Une action est envoyée avec l'intention de provoquer un changement. Ce changement est effectué par le réducteur. Reducer est le seul moyen de modifier les états de Redux, ce qui le rend plus prévisible, centralisé et débuggable. Une fonction de réduction qui gère l'action 'ITEMS_REQUEST' est la suivante -

const reducer = (state = initialState, action) => { //es6 arrow function
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.isLoading
         })
      default:
         return state;
   }
}

Redux a un seul magasin qui contient l'état de l'application. Si vous souhaitez fractionner votre code sur la base de la logique de traitement des données, vous devez commencer à fractionner vos réducteurs au lieu des magasins dans Redux.

Nous discuterons de la façon dont nous pouvons diviser les réducteurs et les combiner avec store plus tard dans ce didacticiel.

Les composants Redux sont les suivants -

Redux suit le flux de données unidirectionnel. Cela signifie que les données de votre application suivront dans un flux de données de liaison unidirectionnel. Au fur et à mesure que l'application se développe et devient complexe, il est difficile de reproduire les problèmes et d'ajouter de nouvelles fonctionnalités si vous n'avez aucun contrôle sur l'état de votre application.

Redux réduit la complexité du code, en imposant la restriction sur la façon et le moment où la mise à jour de l'état peut avoir lieu. De cette façon, la gestion des états mis à jour est facile. Nous connaissons déjà les restrictions comme les trois principes de Redux. Le diagramme suivant vous aidera à mieux comprendre le flux de données Redux -

  • Une action est distribuée lorsqu'un utilisateur interagit avec l'application.

  • La fonction de réducteur racine est appelée avec l'état actuel et l'action distribuée. Le réducteur racine peut diviser la tâche entre des fonctions réductrices plus petites, ce qui renvoie finalement un nouvel état.

  • Le magasin notifie la vue en exécutant ses fonctions de rappel.

  • La vue peut récupérer l'état mis à jour et effectuer un nouveau rendu.

Un magasin est une arborescence d'objets immuable dans Redux. Un magasin est un conteneur d'état qui contient l'état de l'application. Redux ne peut avoir qu'un seul magasin dans votre application. Chaque fois qu'un magasin est créé dans Redux, vous devez spécifier le réducteur.

Voyons comment nous pouvons créer un magasin en utilisant le createStoreméthode de Redux. Il faut importer le package createStore à partir de la bibliothèque Redux qui prend en charge le processus de création de magasin comme indiqué ci-dessous -

import { createStore } from 'redux';
import reducer from './reducers/reducer'
const store = createStore(reducer);

Une fonction createStore peut avoir trois arguments. Voici la syntaxe -

createStore(reducer, [preloadedState], [enhancer])

Un réducteur est une fonction qui renvoie l'état suivant de l'application. Un preloadedState est un argument facultatif et correspond à l'état initial de votre application. Un enhancer est également un argument facultatif. Cela vous aidera à améliorer le magasin avec des capacités tierces.

Un magasin a trois méthodes importantes comme indiqué ci-dessous -

getState

Il vous aide à récupérer l'état actuel de votre boutique Redux.

La syntaxe de getState est la suivante -

store.getState()

envoi

Il vous permet d'envoyer une action pour modifier un état dans votre application.

La syntaxe de la répartition est la suivante -

store.dispatch({type:'ITEMS_REQUEST'})

souscrire

Il vous aide à enregistrer un rappel que le magasin Redux appellera lorsqu'une action a été envoyée. Dès que l'état Redux a été mis à jour, la vue sera automatiquement rendue.

La syntaxe de la répartition est la suivante -

store.subscribe(()=>{ console.log(store.getState());})

Notez que la fonction subscribe renvoie une fonction de désabonnement de l'écouteur. Pour désinscrire l'auditeur, nous pouvons utiliser le code ci-dessous -

const unsubscribe = store.subscribe(()=>{console.log(store.getState());});
unsubscribe();

Les actions sont la seule source d'informations pour le magasin selon la documentation officielle de Redux. Il transporte une charge utile d'informations de votre application à stocker.

Comme indiqué précédemment, les actions sont des objets JavaScript simples qui doivent avoir un attribut type pour indiquer le type d'action effectuée. Cela nous raconte ce qui s'était passé. Les types doivent être définis comme des constantes de chaîne dans votre application comme indiqué ci-dessous -

const ITEMS_REQUEST = 'ITEMS_REQUEST';

En dehors de cet attribut de type, la structure d'un objet action est totalement du ressort du développeur. Il est recommandé de garder votre objet d'action aussi léger que possible et de ne transmettre que les informations nécessaires.

Pour provoquer une modification dans le magasin, vous devez d'abord distribuer une action à l'aide de la fonction store.dispatch (). L'objet d'action est le suivant -

{ type: GET_ORDER_STATUS , payload: {orderId,userId } }
{ type: GET_WISHLIST_ITEMS, payload: userId }

Créateurs d'actions

Les créateurs d'action sont les fonctions qui encapsulent le processus de création d'un objet d'action. Ces fonctions renvoient simplement un objet Js simple qui est une action. Il favorise l'écriture de code propre et contribue à la réutilisation.

Apprenons-en plus sur le créateur d'action qui vous permet d'envoyer une action, ‘ITEMS_REQUEST’qui demande les données de liste des articles de produit au serveur. Pendant ce temps, leisLoading state est rendu vrai dans le réducteur dans le type d'action 'ITEMS_REQUEST' pour indiquer que les éléments sont en cours de chargement et que les données ne sont toujours pas reçues du serveur.

Initialement, l'état isLoading était faux dans le initialStateobjet en supposant que rien ne se charge. Lorsque les données sont reçues dans le navigateur, l'état isLoading sera renvoyé comme faux dans le type d'action 'ITEMS_REQUEST_SUCCESS' dans le réducteur correspondant. Cet état peut être utilisé comme accessoire dans les composants de réaction pour afficher le chargeur / message sur votre page lorsque la demande de données est activée. Le créateur de l'action est le suivant -

const ITEMS_REQUEST = ‘ITEMS_REQUEST’ ;
const ITEMS_REQUEST_SUCCESS = ‘ITEMS_REQUEST_SUCCESS’ ;
export function itemsRequest(bool,startIndex,endIndex) {
   let payload = {
      isLoading: bool,
      startIndex,
      endIndex
   }
   return {
      type: ITEMS_REQUEST,
      payload
   }
}
export function itemsRequestSuccess(bool) {
   return {
      type: ITEMS_REQUEST_SUCCESS,
      isLoading: bool,
   }
}

Pour appeler une fonction de répartition, vous devez passer une action en tant qu'argument à la fonction de répartition.

dispatch(itemsRequest(true,1, 20));
dispatch(itemsRequestSuccess(false));

Vous pouvez distribuer une action en utilisant directement store.dispatch (). Cependant, il est plus probable que vous y accédiez avec la méthode d'assistance react-Redux appeléeconnect(). Vous pouvez aussi utiliserbindActionCreators() méthode pour lier de nombreux créateurs d'action avec la fonction de répartition.

Une fonction est un processus qui prend des entrées appelées arguments et produit une sortie connue sous le nom de valeur de retour. Une fonction est dite pure si elle respecte les règles suivantes -

  • Une fonction renvoie le même résultat pour les mêmes arguments.

  • Son évaluation n'a pas d'effets secondaires, c'est-à-dire qu'elle n'altère pas les données d'entrée.

  • Aucune mutation des variables locales et globales.

  • Il ne dépend pas de l'état externe comme une variable globale.

Prenons l'exemple d'une fonction qui retourne deux fois la valeur passée en entrée de la fonction. En général, il s'écrit f (x) => x * 2. Si une fonction est appelée avec une valeur d'argument 2, alors la sortie serait 4, f (2) => 4.

Écrivons la définition de la fonction en JavaScript comme indiqué ci-dessous -

const double = x => x*2; // es6 arrow function
console.log(double(2));  // 4

Here, double is a pure function.

Conformément aux trois principes de Redux, les modifications doivent être effectuées par une fonction pure, c'est-à-dire un réducteur dans Redux. Maintenant, une question se pose de savoir pourquoi un réducteur doit être une fonction pure.

Supposons que vous souhaitiez distribuer une action dont le type est 'ADD_TO_CART_SUCCESS' pour ajouter un article à votre application de panier en cliquant sur le bouton Ajouter au panier.

Supposons que le réducteur ajoute un article à votre panier comme indiqué ci-dessous -

const initialState = {
   isAddedToCart: false;
}
const addToCartReducer = (state = initialState, action) => { //es6 arrow function
   switch (action.type) {
      case 'ADD_TO_CART_SUCCESS' :
         state.isAddedToCart = !state.isAddedToCart; //original object altered
         return state;
      default:
         return state;
   }
}
export default addToCartReducer ;

Supposons, isAddedToCart est une propriété sur un objet d'état qui vous permet de décider quand désactiver le bouton `` ajouter au panier '' pour l'article en renvoyant une valeur booléenne ‘true or false’. Cela empêche l'utilisateur d'ajouter le même produit plusieurs fois. Maintenant, au lieu de renvoyer un nouvel objet, nous mutons la prop isAddedToCart sur l'état comme ci-dessus. Maintenant, si nous essayons d'ajouter un article au panier, rien ne se passe. Le bouton Ajouter au panier ne sera pas désactivé.

La raison de ce comportement est la suivante -

Redux compare les objets anciens et nouveaux en fonction de l'emplacement mémoire des deux objets. Il attend un nouvel objet du réducteur si un changement s'est produit. Et il s'attend également à récupérer l'ancien objet si aucun changement ne se produit. Dans ce cas, c'est pareil. Pour cette raison, Redux suppose que rien ne s'est passé.

Donc, il est nécessaire qu'un réducteur soit une fonction pure dans Redux. Ce qui suit est une façon de l'écrire sans mutation -

const initialState = {
   isAddedToCart: false;
}
const addToCartReducer = (state = initialState, action) => { //es6 arrow function
   switch (action.type) {
      case 'ADD_TO_CART_SUCCESS' :
         return {
            ...state,
            isAddedToCart: !state.isAddedToCart
         }
      default:
         return state;
   }
}
export default addToCartReducer;

Les réducteurs sont une fonction pure dans Redux. Les fonctions pures sont prévisibles. Les réducteurs sont le seul moyen de changer d'état dans Redux. C'est le seul endroit où vous pouvez écrire de la logique et des calculs. La fonction de réduction acceptera l'état précédent de l'application et de l'action distribuée, calculera l'état suivant et retournera le nouvel objet.

Les quelques opérations suivantes ne doivent jamais être effectuées à l'intérieur du réducteur -

  • Mutation des arguments de fonctions
  • Appels API et logique de routage
  • Appel d'une fonction non pure, par exemple Math.random ()

Voici la syntaxe d'un réducteur -

(state,action) => newState

Continuons l'exemple de l'affichage de la liste des produits sur une page Web, abordés dans le module des créateurs d'actions. Voyons ci-dessous comment écrire son réducteur.

const initialState = {
   isLoading: false,
   items: []
};
const reducer = (state = initialState, action) => {
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.payload.isLoading
         })
      case ‘ITEMS_REQUEST_SUCCESS':
         return Object.assign({}, state, {
            items: state.items.concat(action.items),
            isLoading: action.isLoading
         })
      default:
         return state;
   }
}
export default reducer;

Premièrement, si vous ne définissez pas l'état sur «initialState», Redux appelle le réducteur avec l'état non défini. Dans cet exemple de code, la fonction concat () de JavaScript est utilisée dans 'ITEMS_REQUEST_SUCCESS', qui ne change pas le tableau existant; renvoie à la place un nouveau tableau.

De cette façon, vous pouvez éviter la mutation de l'état. N'écrivez jamais directement à l'État. Dans 'ITEMS_REQUEST', nous devons définir la valeur d'état de l'action reçue.

Il est déjà discuté que nous pouvons écrire notre logique dans le réducteur et la diviser sur la base des données logiques. Voyons comment nous pouvons diviser les réducteurs et les combiner en tant que réducteur de racine lorsque nous traitons une grande application.

Supposons que nous souhaitons concevoir une page Web sur laquelle un utilisateur peut accéder à l'état de la commande de produits et voir les informations de la liste de souhaits. Nous pouvons séparer la logique dans différents fichiers de réducteurs et les faire fonctionner indépendamment. Supposons que l'action GET_ORDER_STATUS soit envoyée pour obtenir le statut de la commande correspondant à un certain identifiant de commande et un identifiant d'utilisateur.

/reducer/orderStatusReducer.js
import { GET_ORDER_STATUS } from ‘../constants/appConstant’;
export default function (state = {} , action) {
   switch(action.type) {
      case GET_ORDER_STATUS:
         return { ...state, orderStatusData: action.payload.orderStatus };
      default:
         return state;
   }
}

De même, supposons que l'action GET_WISHLIST_ITEMS est envoyée pour obtenir les informations de liste de souhaits de l'utilisateur respectives d'un utilisateur.

/reducer/getWishlistDataReducer.js
import { GET_WISHLIST_ITEMS } from ‘../constants/appConstant’;
export default function (state = {}, action) {
   switch(action.type) {
      case GET_WISHLIST_ITEMS:
         return { ...state, wishlistData: action.payload.wishlistData };
      default:
         return state;
   }
}

Maintenant, nous pouvons combiner les deux réducteurs à l'aide de l'utilitaire Redux combineReducers. Les combineReducers génèrent une fonction qui renvoie un objet dont les valeurs sont des fonctions de réduction différentes. Vous pouvez importer tous les réducteurs dans le fichier de réducteur d'index et les combiner en tant qu'objet avec leurs noms respectifs.

/reducer/index.js
import { combineReducers } from ‘redux’;
import OrderStatusReducer from ‘./orderStatusReducer’;
import GetWishlistDataReducer from ‘./getWishlistDataReducer’;

const rootReducer = combineReducers ({
   orderStatusReducer: OrderStatusReducer,
   getWishlistDataReducer: GetWishlistDataReducer
});
export default rootReducer;

Maintenant, vous pouvez passer ce rootReducer à la méthode createStore comme suit -

const store = createStore(rootReducer);

Redux lui-même est synchrone, alors comment le async opérations comme network requesttravailler avec Redux? Ici, les middlewares sont utiles. Comme indiqué précédemment, les réducteurs sont l'endroit où toute la logique d'exécution est écrite. Le réducteur n'a rien à voir avec qui l'exécute, combien de temps il prend ou enregistre l'état de l'application avant et après l'envoi de l'action.

Dans ce cas, la fonction middleware Redux fournit un support pour interagir avec l'action distribuée avant qu'elle n'atteigne le réducteur. Des fonctions middleware personnalisées peuvent être créées en écrivant des fonctions d'ordre supérieur (une fonction qui renvoie une autre fonction), ce qui englobe une certaine logique. Plusieurs middlewares peuvent être combinés pour ajouter de nouvelles fonctionnalités, et chaque middleware ne nécessite aucune connaissance de ce qui s'est passé avant et après. Vous pouvez imaginer des middlewares quelque part entre l'action distribuée et le réducteur.

Généralement, les middlewares sont utilisés pour gérer les actions asynchrones dans votre application. Redux fournit une API appelée applyMiddleware qui nous permet d'utiliser un middleware personnalisé ainsi que des middlewares Redux tels que redux-thunk et redux-promise. Il applique des middlewares pour stocker. La syntaxe d'utilisation de l'API applyMiddleware est -

applyMiddleware(...middleware)

Et cela peut être appliqué pour stocker comme suit -

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(rootReducer, applyMiddleware(thunk));

Les middlewares vous permettront d'écrire un répartiteur d'action qui renvoie une fonction au lieu d'un objet action. Un exemple pour le même est montré ci-dessous -

function getUser() {
   return function() {
      return axios.get('/get_user_details');
   };
}

La distribution conditionnelle peut être écrite à l'intérieur du middleware. Chaque middleware reçoit la distribution du magasin afin de pouvoir distribuer une nouvelle action et les fonctions getState comme arguments pour pouvoir accéder à l'état actuel et renvoyer une fonction. Toute valeur de retour d'une fonction interne sera disponible comme valeur de la fonction de répartition elle-même.

Voici la syntaxe d'un middleware -

({ getState, dispatch }) => next => action

La fonction getState est utile pour décider si de nouvelles données doivent être extraites ou si le résultat du cache doit être renvoyé, en fonction de l'état actuel.

Voyons un exemple d'une fonction de journalisation middleware personnalisée. Il enregistre simplement l'action et le nouvel état.

import { createStore, applyMiddleware } from 'redux'
import userLogin from './reducers'

function logger({ getState }) {
   return next => action => {
      console.log(‘action’, action);
      const returnVal = next(action);
      console.log('state when action is dispatched', getState()); 
      return returnVal;
   }
}

Maintenant, appliquez le middleware de journalisation au magasin en écrivant la ligne de code suivante -

const store = createStore(userLogin , initialState=[ ] , applyMiddleware(logger));

Envoyez une action pour vérifier l'action distribuée et le nouvel état en utilisant le code ci-dessous -

store.dispatch({
   type: 'ITEMS_REQUEST', 
	isLoading: true
})

Un autre exemple de middleware où vous pouvez gérer quand afficher ou masquer le chargeur est donné ci-dessous. Ce middleware affiche le chargeur lorsque vous demandez une ressource et le cache lorsque la demande de ressource est terminée.

import isPromise from 'is-promise';

function loaderHandler({ dispatch }) {
   return next => action => {
      if (isPromise(action)) {
         dispatch({ type: 'SHOW_LOADER' });
         action
            .then(() => dispatch({ type: 'HIDE_LOADER' }))
            .catch(() => dispatch({ type: 'HIDE_LOADER' }));
      }
      return next(action);
   };
}
const store = createStore(
   userLogin , initialState = [ ] , 
   applyMiddleware(loaderHandler)
);

Redux-Devtools nous fournit une plate-forme de débogage pour les applications Redux. Cela nous permet d'effectuer un débogage dans le temps et une édition en direct. Certaines des fonctionnalités de la documentation officielle sont les suivantes -

  • Il vous permet d'inspecter chaque état et chaque charge utile d'action.

  • Il vous permet de remonter le temps en «annulant» les actions.

  • Si vous modifiez le code réducteur, chaque action «par étapes» sera réévaluée.

  • Si les réducteurs lancent, nous pouvons identifier l'erreur et également au cours de quelle action cela s'est produit.

  • Avec persistState () store enhancer, vous pouvez conserver les sessions de débogage à travers les recharges de page.

Il existe deux variantes d'outils de développement Redux comme indiqué ci-dessous -

Redux DevTools - Il peut être installé sous forme de package et intégré à votre application comme indiqué ci-dessous -

https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md#manual-integration

Redux DevTools Extension - Une extension de navigateur qui implémente les mêmes outils de développement pour Redux est la suivante -

https://github.com/zalmoxisus/redux-devtools-extension

Voyons maintenant comment nous pouvons ignorer des actions et remonter le temps à l'aide de l'outil de développement Redux. Les captures d'écran suivantes expliquent les actions que nous avons déployées précédemment pour obtenir la liste des articles. Ici, nous pouvons voir les actions distribuées dans l'onglet inspecteur. Sur la droite, vous pouvez voir l'onglet Démo qui vous montre la différence dans l'arborescence d'état.

Vous vous familiariserez avec cet outil lorsque vous commencerez à l'utiliser. Vous pouvez envoyer une action sans écrire le code réel uniquement à partir de cet outil de plugin Redux. Une option Dispatcher dans la dernière ligne vous aidera avec cela. Vérifions la dernière action où les éléments sont récupérés avec succès.

Nous avons reçu un tableau d'objets en réponse du serveur. Toutes les données sont disponibles pour afficher la liste sur notre page. Vous pouvez également suivre l'état du magasin en même temps en cliquant sur l'onglet d'état en haut à droite.

Dans les sections précédentes, nous avons appris le débogage du voyage dans le temps. Voyons maintenant comment sauter une action et remonter le temps pour analyser l'état de notre application. Lorsque vous cliquez sur n'importe quel type d'action, deux options: «Sauter» et «Sauter» apparaissent.

En cliquant sur le bouton Ignorer sur un certain type d'action, vous pouvez ignorer une action particulière. Il agit comme si l'action ne s'était jamais produite. Lorsque vous cliquez sur le bouton de saut sur un certain type d'action, il vous amènera à l'état où cette action s'est produite et sautera toutes les actions restantes dans l'ordre. De cette façon, vous pourrez conserver l'état lorsqu'une action particulière s'est produite. Cette fonctionnalité est utile pour le débogage et la recherche d'erreurs dans l'application.

Nous avons sauté la dernière action et toutes les données de liste en arrière-plan ont disparu. Cela revient au moment où les données des éléments ne sont pas arrivées et notre application n'a aucune donnée à afficher sur la page. Cela facilite en fait le codage et le débogage.

Tester le code Redux est facile car nous écrivons principalement des fonctions, et la plupart d'entre elles sont pures. On peut donc le tester sans même se moquer d'eux. Ici, nous utilisons JEST comme moteur de test. Il fonctionne dans l'environnement de nœud et n'accède pas au DOM.

Nous pouvons installer JEST avec le code ci-dessous -

npm install --save-dev jest

Avec babel, vous devez installer babel-jest comme suit -

npm install --save-dev babel-jest

Et configurez-le pour utiliser les fonctionnalités babel-preset-env dans le fichier .babelrc comme suit -

{ 
   "presets": ["@babel/preset-env"] 
}
And add the following script in your package.json:
{ 
   //Some other code 
   "scripts": {
      //code
      "test": "jest", 
      "test:watch": "npm test -- --watch" 
   }, 
   //code 
}

Finalement, run npm test or npm run test. Voyons comment nous pouvons écrire des cas de test pour les créateurs et les réducteurs d'action.

Cas de test pour les créateurs d'action

Supposons que vous ayez un créateur d'action comme indiqué ci-dessous -

export function itemsRequestSuccess(bool) {
   return {
      type: ITEMS_REQUEST_SUCCESS,
      isLoading: bool,
   }
}

Ce créateur d'action peut être testé comme indiqué ci-dessous -

import * as action from '../actions/actions';
import * as types from '../../constants/ActionTypes';

describe('actions', () => {
   it('should create an action to check if item is loading', () => { 
      const isLoading = true, 
      const expectedAction = { 
         type: types.ITEMS_REQUEST_SUCCESS, isLoading 
      } 
      expect(actions.itemsRequestSuccess(isLoading)).toEqual(expectedAction) 
   })
})

Cas de test pour les réducteurs

Nous avons appris que le réducteur doit renvoyer un nouvel état lorsque l'action est appliquée. Le réducteur est donc testé sur ce comportement.

Considérez un réducteur comme indiqué ci-dessous -

const initialState = {
   isLoading: false
};
const reducer = (state = initialState, action) => {
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.payload.isLoading
         })
      default:
         return state;
   }
}
export default reducer;

Pour tester le réducteur ci-dessus, nous devons transmettre l'état et l'action au réducteur et renvoyer un nouvel état comme indiqué ci-dessous -

import reducer from '../../reducer/reducer' 
import * as types from '../../constants/ActionTypes'

describe('reducer initial state', () => {
   it('should return the initial state', () => {
      expect(reducer(undefined, {})).toEqual([
         {
            isLoading: false,
         }
      ])
   })
   it('should handle ITEMS_REQUEST', () => {
      expect(
         reducer(
            {
               isLoading: false,
            },
            {
               type: types.ITEMS_REQUEST,
               payload: { isLoading: true }
            }
         )
      ).toEqual({
         isLoading: true
      })
   })
})

Si vous n'êtes pas familiarisé avec l'écriture de cas de test, vous pouvez vérifier les bases de JEST .

Dans les chapitres précédents, nous avons appris ce qu'est Redux et comment cela fonctionne. Vérifions maintenant l'intégration de view part avec Redux. Vous pouvez ajouter n'importe quelle couche de vue à Redux. Nous discuterons également de la bibliothèque de réaction et de Redux.

Disons si divers composants de réaction doivent afficher les mêmes données de différentes manières sans les passer comme accessoire à tous les composants, du composant de niveau supérieur à la descente. Il serait idéal de le stocker en dehors des composants de réaction. Parce que cela permet une récupération plus rapide des données, car vous n'avez pas besoin de transmettre les données à différents composants.

Voyons comment cela est possible avec Redux. Redux fournit le package react-redux pour lier les composants react avec deux utilitaires comme indiqué ci-dessous -

  • Provider
  • Connect

Le fournisseur rend le magasin disponible pour le reste de l'application. La fonction de connexion aide le composant à réagir pour se connecter au magasin, en répondant à chaque changement intervenant dans l'état du magasin.

Jetons un œil à la root index.js fichier qui crée un magasin et utilise un fournisseur qui active le magasin vers le reste de l'application dans une application react-redux.

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducers/reducer'
import thunk from 'redux-thunk';
import App from './components/app'
import './index.css';

const store = createStore(
   reducer,
   window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
   applyMiddleware(thunk)
)
render(
   <Provider store = {store}>
      <App />
   </Provider>,
   document.getElementById('root')
)

Chaque fois qu'une modification se produit dans une application react-redux, mapStateToProps () est appelée. Dans cette fonction, nous spécifions exactement quel état nous devons fournir à notre composant de réaction.

Avec l'aide de la fonction connect () expliquée ci-dessous, nous connectons l'état de ces applications pour réagir au composant. Connect () est une fonction d'ordre élevé qui prend le composant comme paramètre. Il effectue certaines opérations et renvoie un nouveau composant avec des données correctes que nous avons finalement exportées.

Avec l'aide de mapStateToProps (), nous fournissons ces états de magasin comme accessoire à notre composant de réaction. Ce code peut être encapsulé dans un composant conteneur. Le motif est de séparer les problèmes tels que la récupération des données, les problèmes de rendu et la réutilisabilité.

import { connect } from 'react-redux'
import Listing from '../components/listing/Listing' //react component
import makeApiCall from '../services/services' //component to make api call

const mapStateToProps = (state) => {
   return {
      items: state.items,
      isLoading: state.isLoading
   };
};
const mapDispatchToProps = (dispatch) => {
   return {
      fetchData: () => dispatch(makeApiCall())
   };
};
export default connect(mapStateToProps, mapDispatchToProps)(Listing);

La définition d'un composant pour effectuer un appel api dans le fichier services.js est la suivante -

import axios from 'axios'
import { itemsLoading, itemsFetchDataSuccess } from '../actions/actions'

export default function makeApiCall() {
   return (dispatch) => {
      dispatch(itemsLoading(true));
      axios.get('http://api.tvmaze.com/shows')
      .then((response) => {
         if (response.status !== 200) {
            throw Error(response.statusText);
         }
         dispatch(itemsLoading(false));
         return response;
      })
      .then((response) => dispatch(itemsFetchDataSuccess(response.data)))
   };
}

La fonction mapDispatchToProps () reçoit la fonction de répartition en tant que paramètre et vous renvoie les accessoires de rappel sous forme d'objet simple que vous passez à votre composant de réaction.

Ici, vous pouvez accéder à fetchData en tant que accessoire dans votre composant de liste de réaction, qui distribue une action pour effectuer un appel d'API. mapDispatchToProps () est utilisé pour distribuer une action à stocker. Dans react-redux, les composants ne peuvent pas accéder directement au magasin. Le seul moyen est d'utiliser connect ().

Comprenons comment fonctionne le react-redux à travers le diagramme ci-dessous -

STORE - Stocke tout l'état de votre application en tant qu'objet JavaScript

PROVIDER - Rend les magasins disponibles

CONTAINER - Obtenez l'état des applications et fournissez-le comme accessoire aux composants

COMPONENT - L'utilisateur interagit via le composant de vue

ACTIONS - Provoque un changement en magasin, cela peut ou non changer l'état de votre application

REDUCER - Seul moyen de changer l'état de l'application, d'accepter l'état et l'action et de renvoyer l'état mis à jour.

Cependant, Redux est une bibliothèque indépendante et peut être utilisée avec n'importe quelle couche d'interface utilisateur. React-redux est la liaison officielle Redux, UI avec le React. De plus, cela encourage une bonne structure d'application Redux. React-redux implémente en interne l'optimisation des performances, de sorte que le nouveau rendu des composants se produit uniquement lorsque cela est nécessaire.

Pour résumer, Redux n'est pas conçu pour écrire le code le plus court et le plus rapide. Il est destiné à fournir un conteneur de gestion d'état prévisible. Cela nous aide à comprendre quand un certain état a changé ou d'où proviennent les données.

Voici un petit exemple d'application react et Redux. Vous pouvez également essayer de développer de petites applications. Un exemple de code pour le compteur d'augmentation ou de diminution est donné ci-dessous -

Il s'agit du fichier racine qui est responsable de la création du magasin et du rendu de notre composant d'application react.

/src/index.js

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux';
import reducer from '../src/reducer/index'
import App from '../src/App'
import './index.css';

const store = createStore(
   reducer,
   window.__REDUX_DEVTOOLS_EXTENSION__ && 
   window.__REDUX_DEVTOOLS_EXTENSION__()
)
render(
   <Provider store = {store}>
      <App />
   </Provider>, document.getElementById('root')
)

C'est notre composant racine de react. Il est responsable du rendu du composant de conteneur de compteur en tant qu'enfant.

/src/app.js

import React, { Component } from 'react';
import './App.css';
import Counter from '../src/container/appContainer';

class App extends Component {
   render() {
      return (
         <div className = "App">
            <header className = "App-header">
               <Counter/>
            </header>
         </div>
      );
   }
}
export default App;

Ce qui suit est le composant conteneur qui est responsable de fournir l'état de Redux pour réagir composant -

/container/counterContainer.js

import { connect } from 'react-redux'
import Counter from '../component/counter'
import { increment, decrement, reset } from '../actions';

const mapStateToProps = (state) => {
   return {
      counter: state
   };
};
const mapDispatchToProps = (dispatch) => {
   return {
      increment: () => dispatch(increment()),
      decrement: () => dispatch(decrement()),
      reset: () => dispatch(reset())
   };
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

Ci-dessous, le composant de réaction responsable de la partie vue -

/component/counter.js
import React, { Component } from 'react';
class Counter extends Component {
   render() {
      const {counter,increment,decrement,reset} = this.props;
      return (
         <div className = "App">
            <div>{counter}</div>
            <div>
               <button onClick = {increment}>INCREMENT BY 1</button>
            </div>
            <div>
               <button onClick = {decrement}>DECREMENT BY 1</button>
            </div>
            <button onClick = {reset}>RESET</button>
         </div>
      );
   }
}
export default Counter;

Voici les créateurs d'actions responsables de la création d'une action -

/actions/index.js
export function increment() {
   return {
      type: 'INCREMENT'
   }
}
export function decrement() {
   return {
      type: 'DECREMENT'
   }
}
export function reset() {
   return { type: 'RESET' }
}

Ci-dessous, nous avons montré la ligne de code du fichier réducteur qui est responsable de la mise à jour de l'état dans Redux.

reducer/index.js
const reducer = (state = 0, action) => {
   switch (action.type) {
      case 'INCREMENT': return state + 1
      case 'DECREMENT': return state - 1
      case 'RESET' : return 0 default: return state
   }
}
export default reducer;

Au départ, l'application se présente comme suit -

Lorsque je clique sur incrémenter deux fois, l'écran de sortie sera comme indiqué ci-dessous -

Lorsque nous le décrémentons une fois, il affiche l'écran suivant -

Et la réinitialisation ramènera l'application à son état initial qui est la valeur du compteur 0. Ceci est illustré ci-dessous -

Comprenons ce qui se passe avec les outils de développement Redux lorsque la première action d'incrémentation a lieu -

L'état de l'application sera déplacé vers le moment où seule l'action d'incrémentation est envoyée et le reste des actions est ignoré.

Nous vous encourageons à développer une petite application Todo en tant que mission par vous-même et à mieux comprendre l'outil Redux.