|
| 1 | +--- |
| 2 | +mainImage: ../../../images/part-6.svg |
| 3 | +part: 6 |
| 4 | +letter: a |
| 5 | +lang: fr |
| 6 | +--- |
| 7 | + |
| 8 | +<div class="content"> |
| 9 | + |
| 10 | +Jusqu'à présent, nous avons suivi les conventions de gestion d'état recommandées par React. Nous avons placé l'état et les fonctions pour le gérer dans un [niveau supérieur](https://react.dev/learn/sharing-state-between-components) de la structure des composants de l'application. Assez souvent, la majeure partie de l'état de l'application et les fonctions modifiant l'état résident directement dans le composant racine. L'état et ses méthodes de gestion ont ensuite été passés à d'autres composants avec les props. Cela fonctionne jusqu'à un certain point, mais lorsque les applications grandissent, la gestion de l'état devient un défi. |
| 11 | + |
| 12 | +### Architecture Flux |
| 13 | + |
| 14 | +Il y a déjà quelques années, Facebook a développé l'architecture [Flux](https://facebookarchive.github.io/flux/docs/in-depth-overview) pour faciliter la gestion de l'état des applications React. Dans Flux, l'état est séparé des composants React et placé dans ses propres <i>stores</i>. |
| 15 | +L'état dans le store n'est pas modifié directement, mais avec différentes <i>actions</i>. |
| 16 | + |
| 17 | +Lorsqu'une action change l'état du store, les vues sont rerendues: |
| 18 | + |
| 19 | + |
| 20 | + |
| 21 | +Si une action dans l'application, par exemple appuyer sur un bouton, nécessite de changer l'état, le changement est réalisé avec une action. |
| 22 | +Cela provoque à nouveau le rerendu de la vue: |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +Flux offre une manière standard de comment et où l'état de l'application est conservé et comment il est modifié. |
| 27 | + |
| 28 | +### Redux |
| 29 | + |
| 30 | +Facebook a une implémentation pour Flux, mais nous utiliserons la bibliothèque [Redux](https://redux.js.org). Elle fonctionne sur le même principe mais est un peu plus simple. Facebook utilise maintenant également Redux au lieu de leur Flux original. |
| 31 | + |
| 32 | +Nous allons apprendre à connaître Redux en implémentant à nouveau une application de compteur: |
| 33 | + |
| 34 | + |
| 35 | + |
| 36 | +Créez une nouvelle application Vite et installez </i>redux</i> avec la commande |
| 37 | + |
| 38 | +```bash |
| 39 | +npm install redux |
| 40 | +``` |
| 41 | + |
| 42 | +Comme dans Flux, dans Redux, l'état est également stocké dans un [store](https://redux.js.org/basics/store). |
| 43 | + |
| 44 | +L'ensemble de l'état de l'application est stocké dans <i>un</i> objet JavaScript dans le store. Étant donné que notre application n'a besoin que de la valeur du compteur, nous la sauvegarderons directement dans le store. Si l'état était plus compliqué, différentes choses dans l'état seraient sauvegardées comme champs séparés de l'objet. |
| 45 | + |
| 46 | +L'état du store est modifié avec des [actions](https://redux.js.org/basics/actions). Les actions sont des objets, qui ont au moins un champ déterminant le <i>type</i> de l'action. |
| 47 | +Notre application a besoin par exemple de l'action suivante: |
| 48 | + |
| 49 | +```js |
| 50 | +{ |
| 51 | + type: 'INCREMENT' |
| 52 | +} |
| 53 | +``` |
| 54 | + |
| 55 | +Si l'action implique des données, d'autres champs peuvent être déclarés selon les besoins. Cependant, notre application de comptage est si simple que les actions sont suffisantes avec juste le champ de type. |
| 56 | + |
| 57 | +L'impact de l'action sur l'état de l'application est défini à l'aide d'un [reducer](https://redux.js.org/basics/reducers). En pratique, un reducer est une fonction à laquelle sont donnés l'état actuel et une action comme paramètres. Elle <i>retourne</i> un nouvel état. |
| 58 | + |
| 59 | +Définissons maintenant un reducer pour notre application: |
| 60 | + |
| 61 | +```js |
| 62 | +const counterReducer = (state, action) => { |
| 63 | + if (action.type === 'INCREMENT') { |
| 64 | + return state + 1 |
| 65 | + } else if (action.type === 'DECREMENT') { |
| 66 | + return state - 1 |
| 67 | + } else if (action.type === 'ZERO') { |
| 68 | + return 0 |
| 69 | + } |
| 70 | + |
| 71 | + return state |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +Le premier paramètre est l'<i>état</i> dans le store. Le reducer retourne un <i>nouvel état</i> basé sur le type de _l'action_. Ainsi, par exemple, lorsque le type de l'Action est <i>INCREMENT</i>, l'état obtient l'ancienne valeur plus un. Si le type de l'Action est <i>ZERO</i>, la nouvelle valeur de l'état est zéro. |
| 76 | + |
| 77 | +Changeons un peu le code. Nous avons utilisé des instructions if-else pour répondre à une action et changer l'état. Cependant, l'instruction [switch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch) est l'approche la plus courante pour écrire un reducer. |
| 78 | + |
| 79 | +Définissons également une [valeur par défaut](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters) de 0 pour le paramètre <i>état</i>. Maintenant, le reducer fonctionne même si l'état du store n'a pas encore été initialisé. |
| 80 | + |
| 81 | +```js |
| 82 | +// highlight-start |
| 83 | +const counterReducer = (state = 0, action) => { |
| 84 | + // highlight-end |
| 85 | + switch (action.type) { |
| 86 | + case 'INCREMENT': |
| 87 | + return state + 1 |
| 88 | + case 'DECREMENT': |
| 89 | + return state - 1 |
| 90 | + case 'ZERO': |
| 91 | + return 0 |
| 92 | + default: // if none of the above matches, code comes here |
| 93 | + return state |
| 94 | + } |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +Reducer n'est jamais supposé être appelé directement depuis le code de l'application. Reducer est uniquement donné en paramètre à la fonction _createStore_ qui crée le store: |
| 99 | + |
| 100 | +```js |
| 101 | +// highlight-start |
| 102 | +import { createStore } from 'redux' |
| 103 | +// highlight-end |
| 104 | + |
| 105 | +const counterReducer = (state = 0, action) => { |
| 106 | + // ... |
| 107 | +} |
| 108 | + |
| 109 | +// highlight-start |
| 110 | +const store = createStore(counterReducer) |
| 111 | +// highlight-end |
| 112 | +``` |
| 113 | + |
| 114 | +Le store utilise maintenant le reducer pour gérer les <i>actions</i>, qui sont <i>dispatchées</i> ou 'envoyées' au store avec sa méthode [dispatch](https://redux.js.org/api/store#dispatchaction). |
| 115 | + |
| 116 | +```js |
| 117 | +store.dispatch({ type: 'INCREMENT' }) |
| 118 | +``` |
| 119 | + |
| 120 | +Vous pouvez connaître l'état du store en utilisant la méthode [getState](https://redux.js.org/api/store#getstate). |
| 121 | + |
| 122 | +Par exemple, le code suivant: |
| 123 | + |
| 124 | +```js |
| 125 | +const store = createStore(counterReducer) |
| 126 | +console.log(store.getState()) |
| 127 | +store.dispatch({ type: 'INCREMENT' }) |
| 128 | +store.dispatch({ type: 'INCREMENT' }) |
| 129 | +store.dispatch({ type: 'INCREMENT' }) |
| 130 | +console.log(store.getState()) |
| 131 | +store.dispatch({ type: 'ZERO' }) |
| 132 | +store.dispatch({ type: 'DECREMENT' }) |
| 133 | +console.log(store.getState()) |
| 134 | +``` |
| 135 | + |
| 136 | +imprimerait ce qui suit dans la console |
| 137 | + |
| 138 | +<pre> |
| 139 | +0 |
| 140 | +3 |
| 141 | +-1 |
| 142 | +</pre> |
| 143 | + |
| 144 | +car au début, l'état du store est de 0. Après trois actions <i>INCREMENT</i>, l'état est de 3. À la fin, après les actions <i>ZERO</i> et <i>DECREMENT</i>, l'état est de -1. |
| 145 | + |
| 146 | +La troisième méthode importante que le store possède est [subscribe](https://redux.js.org/api/store#subscribelistener), qui est utilisée pour créer des fonctions de rappel que le store appelle chaque fois qu'une action est dispatchée au store. |
| 147 | + |
| 148 | +Si, par exemple, nous ajoutions la fonction suivante à subscribe, <i>chaque changement dans le store</i> serait imprimé dans la console. |
| 149 | + |
| 150 | +```js |
| 151 | +store.subscribe(() => { |
| 152 | + const storeNow = store.getState() |
| 153 | + console.log(storeNow) |
| 154 | +}) |
| 155 | +``` |
| 156 | + |
| 157 | +donc le code |
| 158 | + |
| 159 | +```js |
| 160 | +const store = createStore(counterReducer) |
| 161 | + |
| 162 | +store.subscribe(() => { |
| 163 | + const storeNow = store.getState() |
| 164 | + console.log(storeNow) |
| 165 | +}) |
| 166 | + |
| 167 | +store.dispatch({ type: 'INCREMENT' }) |
| 168 | +store.dispatch({ type: 'INCREMENT' }) |
| 169 | +store.dispatch({ type: 'INCREMENT' }) |
| 170 | +store.dispatch({ type: 'ZERO' }) |
| 171 | +store.dispatch({ type: 'DECREMENT' }) |
| 172 | +``` |
| 173 | + |
| 174 | +provoquerait l'impression suivante |
| 175 | + |
| 176 | +<pre> |
| 177 | +1 |
| 178 | +2 |
| 179 | +3 |
| 180 | +0 |
| 181 | +-1 |
| 182 | +</pre> |
| 183 | + |
| 184 | +Le code de notre application compteur est le suivant. Tout le code a été écrit dans le même fichier (_main.jsx_), donc le <i>store</i> est directement disponible pour le code React. Nous découvrirons plus tard de meilleures façons de structurer le code React/Redux. |
| 185 | + |
| 186 | +```js |
| 187 | +import React from 'react' |
| 188 | +import ReactDOM from 'react-dom/client' |
| 189 | + |
| 190 | +import { createStore } from 'redux' |
| 191 | + |
| 192 | +const counterReducer = (state = 0, action) => { |
| 193 | + switch (action.type) { |
| 194 | + case 'INCREMENT': |
| 195 | + return state + 1 |
| 196 | + case 'DECREMENT': |
| 197 | + return state - 1 |
| 198 | + case 'ZERO': |
| 199 | + return 0 |
| 200 | + default: |
| 201 | + return state |
| 202 | + } |
| 203 | +} |
| 204 | + |
| 205 | +const store = createStore(counterReducer) |
| 206 | + |
| 207 | +const App = () => { |
| 208 | + return ( |
| 209 | + <div> |
| 210 | + <div> |
| 211 | + {store.getState()} |
| 212 | + </div> |
| 213 | + <button |
| 214 | + onClick={e => store.dispatch({ type: 'INCREMENT' })} |
| 215 | + > |
| 216 | + plus |
| 217 | + </button> |
| 218 | + <button |
| 219 | + onClick={e => store.dispatch({ type: 'DECREMENT' })} |
| 220 | + > |
| 221 | + minus |
| 222 | + </button> |
| 223 | + <button |
| 224 | + onClick={e => store.dispatch({ type: 'ZERO' })} |
| 225 | + > |
| 226 | + zero |
| 227 | + </button> |
| 228 | + </div> |
| 229 | + ) |
| 230 | +} |
| 231 | + |
| 232 | +const root = ReactDOM.createRoot(document.getElementById('root')) |
| 233 | + |
| 234 | +const renderApp = () => { |
| 235 | + root.render(<App />) |
| 236 | +} |
| 237 | + |
| 238 | +renderApp() |
| 239 | +store.subscribe(renderApp) |
| 240 | +``` |
| 241 | + |
| 242 | +Il y a quelques points notables dans le code. |
| 243 | +<i>App</i> rend la valeur du compteur en la demandant au store avec la méthode _store.getState()_. Les gestionnaires d'action des boutons <i>dispatch</i> les bonnes actions au store. |
| 244 | + |
| 245 | +Lorsque l'état dans le store est changé, React n'est pas capable de rerendre automatiquement l'application. Ainsi, nous avons enregistré une fonction _renderApp_, qui rend toute l'application, pour écouter les changements dans le store avec la méthode _store.subscribe_. Notez que nous devons immédiatement appeler la méthode _renderApp_. Sans cet appel, le premier rendu de l'application ne se produirait jamais. |
| 246 | + |
| 247 | +### Une note sur l'utilisation de createStore |
| 248 | + |
| 249 | +Les plus observateurs remarqueront que le nom de la fonction createStore est barré. Si vous déplacez la souris sur le nom, une explication apparaîtra |
| 250 | + |
| 251 | + |
| 252 | + |
| 253 | +L'explication complète est la suivante |
| 254 | + |
| 255 | +><i>Nous recommandons d'utiliser la méthode configureStore du package @reduxjs/toolkit, qui remplace createStore.</i> |
| 256 | +> |
| 257 | +><i>Redux Toolkit est notre approche recommandée pour écrire la logique Redux aujourd'hui, y compris la configuration du store, les reducers, la récupération de données et plus encore.</i> |
| 258 | +> |
| 259 | +><i>Pour plus de détails, veuillez lire cette page de documentation Redux: <https://redux.js.org/introduction/why-rtk-is-redux-today></i> |
| 260 | +> |
| 261 | +><i>configureStore de Redux Toolkit est une version améliorée de createStore qui simplifie la configuration et aide à éviter les bugs courants.</i> |
| 262 | +> |
| 263 | +><i>Vous ne devriez pas utiliser le package redux core seul aujourd'hui, sauf à des fins d'apprentissage. La méthode createStore du package redux core ne sera pas retirée, mais nous encourageons tous les utilisateurs à migrer vers l'utilisation de Redux Toolkit pour tout le code Redux.</i> |
| 264 | +
|
| 265 | +Donc, au lieu de la fonction <i>createStore</i>, il est recommandé d'utiliser la fonction un peu plus "avancée" <i>configureStore</i>, et nous l'utiliserons également lorsque nous aurons atteint la fonctionnalité de base de Redux. |
| 266 | + |
| 267 | +Note à part: <i>createStore</i> est défini comme "déprécié", ce qui signifie généralement que la fonctionnalité sera retirée dans une version plus récente de la bibliothèque. L'explication ci-dessus et la discussion de [celle-ci](https://stackoverflow.com/questions/71944111/redux-createstore-is-deprecated-cannot-get-state-from-getstate-in-redux-ac) révèlent que <i>createStore</i> ne sera pas retiré, et il a été donné le statut <i>déprécié</i>, peut-être pour des raisons légèrement incorrectes. Donc, la fonction n'est pas obsolète, mais aujourd'hui, il y a une nouvelle manière plus préférable de faire presque la même chose. |
| 268 | + |
| 269 | +### Notes sur Redux |
| 270 | + |
| 271 | +Nous visons à modifier notre application de notes pour utiliser Redux pour la gestion de l'état. Cependant, couvrons d'abord quelques concepts clés à travers une application de notes simplifiée. |
| 272 | + |
| 273 | +La première version de notre application est la suivante |
| 274 | + |
| 275 | +```js |
| 276 | +const noteReducer = (state = [], action) => { |
| 277 | + if (action.type === 'NEW_NOTE') { |
| 278 | + state.push(action.payload) |
| 279 | + return state |
| 280 | + } |
| 281 | + |
| 282 | + return state |
| 283 | +} |
| 284 | + |
| 285 | +const store = createStore(noteReducer) |
| 286 | + |
| 287 | +store.dispatch({ |
| 288 | + type: 'NEW_NOTE', |
| 289 | + payload: { |
| 290 | + content: 'the app state is in redux store', |
| 291 | + important: true, |
| 292 | + id: 1 |
| 293 | + } |
| 294 | +}) |
| 295 | + |
| 296 | +store.dispatch({ |
| 297 | + type: 'NEW_NOTE', |
| 298 | + payload: { |
| 299 | + content: 'state changes are made with actions', |
| 300 | + important: false, |
| 301 | + id: 2 |
| 302 | + } |
| 303 | +}) |
| 304 | + |
| 305 | +const App = () => { |
| 306 | + return( |
| 307 | + <div> |
| 308 | + <ul> |
| 309 | + {store.getState().map(note=> |
| 310 | + <li key={note.id}> |
| 311 | + {note.content} <strong>{note.important ? 'important' : ''}</strong> |
| 312 | + </li> |
| 313 | + )} |
| 314 | + </ul> |
| 315 | + </div> |
| 316 | + ) |
| 317 | +} |
| 318 | +``` |
| 319 | + |
| 320 | +</div> |
0 commit comments