l'implémentation moderne (mai 2020) de react et redux demande de se pencher sur plusieurs outils :
- les hooks react
- Le paquet Redux Toolkit
- le paquet react-redux
Les hooks
Voir la documentation officielle en français
Les Hooks sont arrivés avec React 16.8. Ils permettent de bénéficier d’un état local et d’autres fonctionnalités de React sans avoir à écrire une classe.
Les Hooks sont des fonctions qui permettent de « se brancher » sur la gestion d’état local et de cycle de vie de React depuis des fonctions composants (à opposer aux class composants). Les Hooks ne fonctionnent pas dans des classes : ils vous permettent d’utiliser React sans classes.
React fournit quelques Hooks prédéfinis comme useState.
hook d'état : useState
Ex d'utilisation de la fonction useState :
import React, { useState } from 'react'; function Counter2(props) { const [count, setCount] = useState(0); return ( <> <div>Compteur : {count}</div> <button onClick={() => setCount(count + 1)}>Augmenter</button> </> ); } export default Counter2;
Le code comparable en utilisant les class donne :
import React, { Component } from 'react'; class Counter2 extends Component { state = { count: 0 }; increaseCount = () => { this.setState({ count: this.state.count + 1 }); } render() { return ( <div> <div>Compteur : {this.state.count}</div> <button onClick={this.increaseCount}>Augmenter</button> </div> ); } } export default Counter2;
Avec l'utilisation des hooks avec des "function component", on voit :
- que le code est plus concis,
- qu'il n'y a plus à se soucier de "this".
Hook d’effet : useEffect
Vous avez surement déjà réalisé un chargement de données distantes, des abonnements ou des modifications manuelles sur le DOM depuis un composant React. Nous appelons ces opérations « effets de bord » (ou effets pour faire court) parce qu’elles peuvent affecter d’autres composants et ne peuvent être réalisées pendant l’affichage.
Le Hook d’effet, useEffect, permet aux fonctions composants de gérer des effets de bord. Il joue le même rôle que componentDidMount, componentDidUpdate, et componentWillUnmount dans les classes React, mais au travers d’une API unique. (Nous verrons des exemples comparant useEffect à ces méthodes dans Utiliser le Hook d’effet .)
Par exemple, ce composant change le titre du document après que React a mis à jour le DOM :
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // Équivalent à componentDidMount plus componentDidUpdate : useEffect(() => { // Mettre à jour le titre du document en utilisant l'API du navigateur document.title = `Vous avez cliqué ${count} fois`; }); return ( <div> <p>Vous avez cliqué {count} fois</p> <button onClick={() => setCount(count + 1)}> Cliquez ici </button> </div> ); }
Lorsque vous appelez useEffect, vous dites à React de lancer votre fonction d’« effet » après qu’il a mis à jour le DOM. Les effets étant déclarés au sein du composant, ils ont accès à ses props et son état. Par défaut, React exécute les effets après chaque affichage, y compris le premier.
Redux Toolkit
Le package Redux Toolkit cherche à standardiser la manière d'écrire la logique Redux. Il a été conçu pour répondre aux affirmations suivantes :
- "Configurer un store Redux est trop compliqué"
- "Je dois ajouter beaucoup de packages à Redux pour avoir finalement quelque chose d'utilisable"
- "Redux nécessite trop de "boilerplate code" (que l'on peut traduire par du code à la cxx)"
Fonctions principales :
configureStore()
Encapsule createStore pour fournir des options de configuration simplifiées et de bonnes valeurs par défaut. Il peut combiner automatiquement vos réducteurs de tranche (cf createSlice plus bas), ajoute le middleware Redux que vous fournissez, inclut redux-thunk par défaut et permet l'utilisation de l'extension Redux DevTools.
Gestions des actions asynchrones
Redux Thunk middleware est un module redux qui permet d’écrire des fonctions de création d’actions qui retournent une fonction ou une Promise au lieu de retourner une simple action. Cette fonction retournée reçoit les méthodes dispatch et getState du store en paramètre. Ce mécanisme permet de retarder l’exécution du dispatch d’une action. Ce mécanisme est utile lors de l’utilisation de code asynchrone comme un appel a fetch.
createSlice()
On peut voir une "tranche de reducer" (slice Reducer) comme un reducer responsable d'une partie (une tranche !) du state de l'application.
La fonction createSlice attend en argument un objet qui contient des fonctions "reducer", un nom de "slice" (tranche) et une valeur de state initiale. Génère automatiquement une tranche de reducer avec les "action creators" et les "actions types."
Paramètres
createSlice attend en paramètre un object unique avec les options suivantes :
function createSlice({ // Un objet de "case reducers". Les clés (noms des propriétés) correspondront aux noms des actions générées reducers: { increment: state => { // Redux Toolkit permet d'écrire avec une logique de "mutation" dans // les reducers grâce à la librairie Immer state.value += 1; }, decrement: state => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; }, }, // Le state initial pour le reducer initialState: { value: 0, }, // Le nom utiliser dans le type d'action name: 'counter', // Un autre objet de "case reducers". Les clés (noms des propriétés) devraient être d'autres types d'action. extraReducers?: | Object<string, ReducerFunction> | ((builder: ActionReducerMapBuilder<State>) => void) })
Return Value
createSlice retourne un objet du type :
{ name : string, reducer : ReducerFunction, actions : Object<string, ActionCreator>, }
Redux et React
Documentation
Installation de react-redux avec Redux Toolkit
Installer react-redux : La manière recommandée de commencer une nouvelle application avec React et Redux, est d'utiliser le template officiel qui tire avantage de Redux Toolkit et de l'intégration du module react-redux avec les composants de React
npx create-react-app test --template redux
Architecture d'une application react redux avec le template officiel
Container principal
cf src/index.js
Le compornet "Provider" fourni par "react-redux" va "encapsuler" tous les components , condition sine qua-non pour faire fonctionner react-redux.
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import store from './app/store'; import { Provider } from 'react-redux'; import * as serviceWorker from './serviceWorker'; ReactDOM.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById('root') );
C'est grâce à la fonction createStore que le "store" va être créé (cf src/app/store.js) et accessibles par tous les components enfants
Les méthodes fondamentales
Il a été choisi ici d'introduire directement les hooks useSelector et useDispatch alors que la fonction historiquement utilisée pour connecter un composant React à un Store Redux est "connect" qui attend en arguments mapStateToProps, et mapDispatchToProps.
useSelector
... où comment récupérer une partie du state du store Redux
Le sélecteur sera appelé avec le state du store Redux comme seul argument. Il va renvoyer uniquement la partie du store Redux dont le composant a besoin.
Le sélecteur sera exécuté à chaque rendu du composant fonction (sauf si sa référence n'a pas changé depuis un rendu précédent du composant afin qu'un résultat mis en cache puisse être renvoyé par le hook sans réexécuter le sélecteur). useSelector () s'abonne également à la boutique Redux et exécute votre sélecteur chaque fois qu'une action est envoyée.
// Dans counterSlice export const selectCount = state => state.counter.value; ... // Dans Counter.js const count = useSelector(selectCount);
useDispatch
... où comment dispatcher une action au store Redux depuis un composant React.
useDispatch retourne une référence à la function dispatch du "store" Redux. Couplé avec Redux Toolkit, useDispatch fait appel aux "action creator" créés via createSlice. Vous devrez donc donner en paramètre une fonction déjà définie dans votre "tranche de reducer" pour un appel direct au reducer. L'argument passé au "reducer" sera considéré comme étant le '"payload".
Ex :
dispatch(incrementByAmount(2));//payload: 2
Si vous ne donnez pas en argument une fonction déjà définie dans votre "tranche de reducer", ce sera l'occasion de faire appel à un autre "action creator" qui pourra lui faire appel à des fonctions asynchrones avant de dispatcher de façon classique une action.
Ex :
// Dans Counter.js dispatch(incrementAsync(Number(incrementAmount) || 0)) ... // Dans counterSlice.js export const incrementAsync = amount => dispatch => { setTimeout(() => { dispatch(incrementByAmount(amount)); }, 1000); };
Flux d'une application React-Redux
Middleware
Le middlerware se situe, comme son nom l'indique, au milieu !
Losqu'une action est "dispatchée", elle est ensuite envoyée aux reducer (ou au root reducer).
Le middle ware est la partie de code qui sera exécutée après que l'action a été dispatchée et avant que le "root reducer" ne soit appelé.
Le middleware peut être utilisé pour :
- des appels à des API
- des reports d'erreur
- de l'analytics
- des demandes d'autorisation,
- ...