|
1 |
| -# Etape 2 |
| 1 | +# Etape 2 - Développer une application React |
2 | 2 |
|
3 |
| -TODO |
| 3 | +## Pré-requis |
| 4 | + |
| 5 | +Vous devez maîtriser les étapes 0 et 1 du workshop afin de pouvoir réaliser l'étape 2. |
| 6 | + |
| 7 | +Le code disponible dans cette étape correspond au résultat attendu des étapes 0 et 1. Vous pouvez partir de cette base pour développer l'étape 2. |
| 8 | + |
| 9 | +## Objectif |
| 10 | + |
| 11 | +L'objectif de cette étape est de développer une application permettant d'afficher la fiche descriptive d'un vin, chaque vin étant catégorisé dans une région viticole. |
| 12 | + |
| 13 | +Voici une maquette de l'application : |
| 14 | + |
| 15 | +* Une première colonne listant les régions viticoles. Chaque région est sélectionnable par un clic. |
| 16 | +* Une deuxième colonne listant les vins de la région viticole sélectionnée. Chaque vin est sélectionnable par un clic. |
| 17 | +* Une troisième colonne affichant la fiche descriptive du vin sélectionné, contenant : |
| 18 | + * Le nom du vin |
| 19 | + * Le type de vin (rouge, blanc, rosé, etc ...) |
| 20 | + * L'appellation du vin (nom de l'appellation et région) |
| 21 | + * La liste des cépages du vin |
| 22 | + * Une image du vin |
4 | 23 |
|
5 | 24 | 
|
| 25 | + |
| 26 | +Avant de partir la tête baissée dans le développement, il est nécessaire de se poser quelques questions : |
| 27 | + |
| 28 | +* Quels sont les composants de mon applications et comment sont-ils liés ? |
| 29 | +* Quelles sont les données gérées par mes composants ? Ces données sont-elles gérées via les `props` ou via le `state` ? |
| 30 | +* Quelles sont les interactions entre mes composants ? Quels événements doivent-être gérés ? |
| 31 | +* Ok, je suis prêt pour développer, mais par quoi je commence ? |
| 32 | + |
| 33 | +## Hiérarchie de composants |
| 34 | + |
| 35 | +La première chose à faire est de mettre son cerveau en **mode React** et de **penser composants"** :-) |
| 36 | + |
| 37 | +A partir de la maquette fournie, nous pouvons identifier les principaux composants de notre application : |
| 38 | + |
| 39 | +* `Regions` : le composant gérant la liste des régions, |
| 40 | +* `WineList` : le composant gérant la liste des vins, |
| 41 | +* `Wine` : le composant gérant la fiche descriptive d'un vin, |
| 42 | +* `WineApp` : le composant parent permettant d'assembler les autres composants, qui représente au final notre application. |
| 43 | + |
| 44 | + |
| 45 | + |
| 46 | +## Props et state |
| 47 | + |
| 48 | +Les données gérées par l'application sont les suivantes : |
| 49 | + |
| 50 | +* la liste des régions viticoles, |
| 51 | +* la référence à la région sélectionnée dans la liste des régions, |
| 52 | +* la liste des vins de la régions viticole sélectionnée, |
| 53 | +* la description du vin sélectionné dans la liste des vins. |
| 54 | + |
| 55 | +Il reste à savoir de quelle manière ces données doivent être gérées au niveau de chaque composant : via les `props` ou via le `state` ? |
| 56 | + |
| 57 | +Les `props` d'un composant sont immutables contrairement au `state` qui lui est mutable. Il est donc généralement conseillé de définir deux types de composants dans une application React : |
| 58 | + |
| 59 | +* Les composants dédiés purement à la présentation, qui s'appuieront uniquement sur leur `props`. Nous les surnommeront `Dumb components`. |
| 60 | +* Les composants plutôt orientés "conteneurs", qui sont responsable du fonctionnement de l'application, qui s'appuient fortement sur leur `state`. Nous les surnommeront `Smart components`. |
| 61 | + |
| 62 | +Vous pouvez lire un article très intéressant sur le sujet : [Smart and Dumb Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.a0czhe4g8) |
| 63 | + |
| 64 | +Dans notre cas, `Regions`, `WineList` et `Wine` sont des composants orientés présentation et utiliseront uniquement leurs `props`. `WineApp` est le conteneur et utilisera donc son `state`. |
| 65 | + |
| 66 | +## Interactions entre les composants |
| 67 | + |
| 68 | +Les interactions entre les composants sont les suivantes : |
| 69 | + |
| 70 | +* La sélection d'une région provoque la mise à jour de la liste des vins (affichage de la liste des vins de la régions sélectionnée) et de la description du vin (affichage de la description du premier vin de la liste) |
| 71 | +* La sélection d'un vin provoque la mise à jour de la description du vin. |
| 72 | + |
| 73 | +## Par où commencer le développement ? |
| 74 | + |
| 75 | +Par expérience, une bonne façon de démarrer le développement d'une application React est la suivante : |
| 76 | + |
| 77 | +* Construire une version statique de l'application en créant l'ensemble des composants définis lors des précédentes étapes de conception. |
| 78 | +* Définir l'état initial des composants de type conteneur et utliser cet état dans le rendu des composants. |
| 79 | +* Charger dynamiquement les données depuis l'API (appels Ajax) et mettre à jour l'état des composants. |
| 80 | +* Gérer les événements pour rendre l'application interactive. |
| 81 | + |
| 82 | +### Version statique de l'application |
| 83 | + |
| 84 | +### Dumbs |
| 85 | + |
| 86 | +Créez l'ensemble des composants de type présentation: `Regions`, `WineList`, et `Wine` et implémentez la méthode `render()` de chacun d'eux, en vous appuyant sur les `props`. |
| 87 | + |
| 88 | +Par exemple pour le composant `Regions` : |
| 89 | + |
| 90 | +```javascript |
| 91 | +const Regions = React.createClass({ |
| 92 | + render () { |
| 93 | + return ( |
| 94 | + <div> |
| 95 | + { this.props.regions.map(region => <div key={region}>{region}</div>) } |
| 96 | + </div> |
| 97 | + ) |
| 98 | + } |
| 99 | +}) |
| 100 | + |
| 101 | +export default Regions |
| 102 | +``` |
| 103 | + |
| 104 | +*Lors du rendu d'une liste de composants, React a besoin d'une propriété unique `key` sur chaque composant de la liste. Pour plus de détails techniques, [lisez la documentation](https://facebook.github.io/react/docs/reusable-components.html)* |
| 105 | + |
| 106 | +##### PropTypes |
| 107 | + |
| 108 | +Il est possible de définir plus précisément le format attendu dans les `props` d'un composant, grâce aux [`PropTypes`](https://facebook.github.io/react/docs/reusable-components.html). |
| 109 | + |
| 110 | +Par exemple sur notre composant `Wine` qui gère l'affichage de la description d'un vin : |
| 111 | + |
| 112 | +```javascript |
| 113 | +const Wine = React.createClass({ |
| 114 | + propTypes: { |
| 115 | + wine: PropTypes.shape({ |
| 116 | + id: PropTypes.string, |
| 117 | + name: PropTypes.string, |
| 118 | + type: PropTypes.oneOf(['Rouge', 'Blanc', 'Rosé', 'Effervescent', 'Moelleux']), |
| 119 | + appellation: PropTypes.shape({ |
| 120 | + name: PropTypes.string, |
| 121 | + region: PropTypes.string |
| 122 | + }), |
| 123 | + grapes: PropTypes.arrayOf(PropTypes.string) |
| 124 | + }) |
| 125 | + }, |
| 126 | + // ... |
| 127 | +} |
| 128 | +``` |
| 129 | +
|
| 130 | +Définissez les `propTypes` de chaque composant, en vous appuyant sur le format des données remontées par l'API. |
| 131 | +
|
| 132 | +#### Smarts |
| 133 | +
|
| 134 | +Créez le composant conteneur `WineApp` qui permet d'assembler l'ensemble des composants. |
| 135 | +
|
| 136 | +C'est ce composant qui sera utilisé pour effectuer le rendu de l'application dans le DOM : |
| 137 | +
|
| 138 | +```javascript |
| 139 | +import React from 'react'; |
| 140 | +import ReactDOM from 'react-dom'; |
| 141 | + |
| 142 | +import WineApp from './components/wine-app'; |
| 143 | + |
| 144 | +ReactDOM.render( |
| 145 | + <WineApp />, |
| 146 | + document.getElementById('main') |
| 147 | +); |
| 148 | +``` |
| 149 | +
|
| 150 | +Afin de pouvoir tester rapidement de rendu de l'application, vous pouvez utiliser des valeurs "en dur" pour alimenter les `props` des composants. Par exemple : |
| 151 | +
|
| 152 | +```javascript |
| 153 | +const WineApp = React.createClass({ |
| 154 | + render() { |
| 155 | + return ( |
| 156 | + ... |
| 157 | + <Regions regions={["Bordeaux", "Bourgogne"]} /> |
| 158 | + ... |
| 159 | + } |
| 160 | + } |
| 161 | +}) |
| 162 | +``` |
| 163 | +
|
| 164 | +### Etat initial des composants |
| 165 | +
|
| 166 | +L'étape suivante consiste à définir l'état initial des composants de type conteneur, donc `WineApp` dans notre cas. |
| 167 | +
|
| 168 | +Cela se fait via la méthode `getInitialState()` du composant : |
| 169 | +
|
| 170 | +```javascript |
| 171 | +const WineApp = React.createClass({ |
| 172 | + getInitialState() { |
| 173 | + return { |
| 174 | + regions: [], |
| 175 | + selectedRegion: null, |
| 176 | + wines:[], |
| 177 | + selectedWine: null |
| 178 | + }; |
| 179 | + }, |
| 180 | + // ... |
| 181 | +}) |
| 182 | +``` |
| 183 | +
|
| 184 | +Utilisez ensuite le `state` dans le rendu. Par exemple pour alimenter la liste des régions : |
| 185 | +
|
| 186 | +```javascript |
| 187 | +const WineApp = React.createClass({ |
| 188 | + // ... |
| 189 | + render() { |
| 190 | + return ( |
| 191 | + ... |
| 192 | + <Regions regions={this.state.regions} /> |
| 193 | + ... |
| 194 | + } |
| 195 | + } |
| 196 | +}) |
| 197 | +``` |
| 198 | +
|
| 199 | +### Récupération des données via l'API |
| 200 | +
|
| 201 | +Les données doivent être récupérées en Ajax au travers de l'API mise à disposition. |
| 202 | +
|
| 203 | +Dans un composant React, les appels Ajax se font généralement dans la méthode `componentDidMount()` du [cycle de vie du composant](https://facebook.github.io/react/docs/component-specs.html#lifecycle-methods). |
| 204 | +
|
| 205 | +Nous utilisons `fetch` pour effectuer les appels Ajax. Par exemple pour charger la liste des régions : |
| 206 | +
|
| 207 | +```javascript |
| 208 | +const WineApp = React.createClass({ |
| 209 | + // ... |
| 210 | + componentDidMount() { |
| 211 | + fetch('http://localhost:3000/api/regions') |
| 212 | + .then(r => r.json()) |
| 213 | + .then(data => { |
| 214 | + this.setState({ |
| 215 | + regions: data, |
| 216 | + selectedRegion: data[0] |
| 217 | + }); |
| 218 | + // TODO Charger les vins de la région : this.loadWinesByRegion(data[0]); |
| 219 | + }) |
| 220 | + .catch(response => { |
| 221 | + console.error(response); |
| 222 | + }); |
| 223 | + }, |
| 224 | + // ... |
| 225 | +}) |
| 226 | +``` |
| 227 | +
|
| 228 | +*La méthode `setState()` permet de mettre à jour l'état du composant, ce qui provoque son re-rendu.* |
| 229 | +
|
| 230 | +Complétez le code du composant `WineApp` afin de gérer l'ensemble des appels Ajax permettant d'alimenter correctement le `state`. |
| 231 | +
|
| 232 | +
|
| 233 | +### Gestion des événements |
| 234 | +
|
| 235 | +La dernière étape consiste à gérer les événements afin de rendre l'application interactive. |
| 236 | +
|
| 237 | +Ce sont les composants de type présentation qui vont capter les événements. Mais ce ne sont pas eux qui effectueront le traitement lié aux événements : ils se contenteront de déléguer ce traitement au composant parent, via une fonction de type "handler" passée via les `props`. |
| 238 | +
|
| 239 | +Par exemple pour le composant `Regions`, il faut déterminer ce que l'on fait lorsque l'utilisateur sélectionne une région (événement ̀`onClick`). |
| 240 | +
|
| 241 | +Au niveau du composant `Regions` cela donne simplement : |
| 242 | +
|
| 243 | +```javascript |
| 244 | +const Regions = React.createClass({ |
| 245 | + handleRegionClick(event) { |
| 246 | + this.props.onRegionChange(event.target.textContent); |
| 247 | + }, |
| 248 | + |
| 249 | + render () { |
| 250 | + return ( |
| 251 | + <div> |
| 252 | + { |
| 253 | + this.props.regions.map(region => |
| 254 | + <div key={region} onClick={this.handleRegionClick}> |
| 255 | + {region} |
| 256 | + </div> |
| 257 | + ) |
| 258 | + } |
| 259 | + </div> |
| 260 | + ) |
| 261 | + } |
| 262 | +}) |
| 263 | +``` |
| 264 | +
|
| 265 | +Et au niveau du composant `WineApp` : |
| 266 | +
|
| 267 | +```javascript |
| 268 | +const WineApp = React.createClass({ |
| 269 | + // ... |
| 270 | + handleRegionChange(region) { |
| 271 | + this.setState({ |
| 272 | + selectedRegion: region |
| 273 | + }); |
| 274 | + // TODO Recharger les vins de la région sélectionnée : this.loadWinesByRegion(region); |
| 275 | + }, |
| 276 | + |
| 277 | + render() { |
| 278 | + return ( |
| 279 | + ... |
| 280 | + <Regions regions={this.state.regions} onRegionChange={this.handleRegionChange} /> |
| 281 | + ... |
| 282 | + } |
| 283 | + } |
| 284 | +}) |
| 285 | +``` |
0 commit comments