Penser en React

React est, à notre avis, la meilleure façon de créer des applis web vastes et performantes en JavaScript. Il a très bien tenu le coup pour nous, à Facebook et Instagram.

L’un des nombreux points forts de React, c’est la façon dont il vous fait penser aux applis pendant que vous les créez. Dans ce document, nous vous guiderons à travers l’élaboration avec React d’un tableau de données de produits proposant filtrage et recherche.

Commençons par une maquette

Imaginez que nous avons déjà une API JSON et une maquette de notre designer. La maquette ressemble à ceci :

Maquette

Notre API JSON renvoie des données qui ressemblent à ceci :

[
  {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];

Étape 1 : décomposer l’interface utilisateur en une hiérarchie de composants

Pour commencer, dessinez des cases autour de chaque composant (et sous-composant) sur la maquette, et attribuez un nom à chacune. Si vous travaillez avec un designer, il se peut qu’elle l’ait déjà fait, alors allez lui parler ! Ses noms de calques Photoshop pourraient devenir les noms de vos composants React !

Mais comment savoir quelles parties devraient disposer de leurs propres composants ? Utilisez simplement les mêmes techniques que lorsque vous décidez de créer une nouvelle fonction ou un nouvel objet. L’une de ces techniques est le principe de responsabilité unique, qui stipule qu’un composant ne devrait idéalement faire qu’une seule chose. S’il finit par grossir, il devrait être décomposé en sous-composants plus petits.

Comme vous affichez souvent un modèle de données JSON à un utilisateur, vous constaterez que si votre modèle a été correctement construit, votre interface utilisateur (et donc la structure de vos composants) correspondra aisément. En effet, l’interface utilisateur (UI) et les modèles de données tendent à adhérer à la même architecture d’information, ce qui signifie que le travail de séparation de votre UI en composants est souvent trivial. Il suffit de la décomposer en composants qui représentent juste un élément de votre modèle de données.

Schéma des composants

Vous pouvez voir que nous avons cinq composants dans notre petite appli. Nous avons mis en italiques les données que chaque composant représente.

  1. FilterableProductTable (orange) : contient l’intégralité de l’exemple
  2. SearchBar (bleu) : reçoit toutes les données saisies par l’utilisateur
  3. ProductTable (vert) : affiche et filtre la collection de données en fonction des données saisies par l’utilisateur
  4. ProductCategoryRow (turquoise) : affiche un titre pour chaque catégorie
  5. ProductRow (rouge) : affiche une ligne pour chaque produit

Si vous regardez ProductTable, vous verrez que l’en-tête du tableau (contenant les titres “Name” et “Price”) n’a pas son propre composant. C’est une question de préférence, et honnêtement les deux se valent. Dans cet exemple, nous l’avons laissé au sein de ProductTable car il fait partie de l’affichage de la collection de données, qui est de la responsabilité de ProductTable. Cependant, si cet en-tête devenait complexe (à savoir, si nous devions ajouter des options de tri), il deviendrait logique d’en faire son propre composant ProductTableHeader.

Maintenant que nous avons identifié les composants dans notre maquette, organisons-les en hiérarchie. C’est facile. Les composants qui apparaissent dans un autre composant sur la maquette doivent apparaître comme enfants dans cette hiérarchie :

  • FilterableProductTable

    • SearchBar
    • ProductTable

      • ProductCategoryRow
      • ProductRow

Étape 2 : construire une version statique avec React

Voir le Pen Penser en React : Étape 2 sur CodePen.

Maintenant que vous avez votre hiérarchie de composants, il est temps d’implémenter votre appli. La façon la plus simple consiste à construire une version qui prend votre modèle de données et affiche une UI inerte. Il est préférable de découpler ces processus, car la construction d’une version statique nécessite beaucoup de code et aucune réflexion, alors qu’ajouter de l’interactivité demande beaucoup de réflexion et peu de code. Nous verrons pourquoi.

Pour créer une version statique de votre appli qui affiche votre modèle de données, vous devrez créer des composants qui en réutilisent d’autres et transmettent les données au moyen des props. Les props sont un moyen de transmettre des données de parent à enfant. Si vous êtes à l’aise avec le concept d’état local, n’utilisez pas d’état local du tout pour construire cette version statique. L’état local est réservé à l’interactivité, c’est-à-dire aux données qui évoluent dans le temps. Comme il s’agit d’une version statique de l’appli, vous n’en avez pas besoin.

Vous pouvez construire l’appli en partant de l’extérieur ou de l’intérieur. En d’autres termes, vous pouvez aussi bien commencer par construire les composants les plus hauts dans la hiérarchie (dans notre cas, FilterableProductTable), que par ceux les plus bas (ProductRow). Dans des exemples plus simples, il est généralement plus facile de partir de l’extérieur, et sur des projets plus importants, il est plus facile de partir de l’intérieur et d’écrire les tests au fil de la construction.

À la fin de cette étape, vous disposerez d’une bibliothèque de composants réutilisables qui afficheront votre modèle de données. Les composants n’auront que des méthodes render() puisque c’est une version statique de l’application. Le composant au sommet de la hiérarchie (FilterableProductTable) prendra votre modèle de données en tant que prop. Si vous modifiez les données et appelez ReactDOM.render() à nouveau, l’UI sera mise à jour. On comprend facilement comment votre UI est mise à jour et où y apporter des modifications, car il n’y a rien de compliqué. Le flux de données unidirectionnel de React (également appelé liaison unidirectionnelle) permet de maintenir la modularité et la rapidité de l’ensemble.

Jetez un œil à la doc de React si vous avez besoin d’aide pour cette étape.

Petit entracte : props ou état ?

Il existe deux types de données dans le « modèle » de React : les props et l’état local. Il est important de bien comprendre la distinction entre les deux ; utilisez la doc officielle de React si vous n’êtes pas sûr·e de la différence.

Étape 3 : déterminer le contenu minimal (mais suffisant) de l’état de l’UI

Pour rendre votre UI interactive, vous devez pouvoir déclencher des modifications à votre modèle de données. React vous facilite la tâche avec l’état local.

Afin de construire correctement votre appli, vous devez d’abord penser à l’état modifiable minimal dont votre appli a besoin. La règle est simple : ne vous répétez pas (Don’t Repeat Yourself, aussi désigné par l’acronyme DRY, NdT). Déterminez la représentation la plus minimale possible de l’état dont votre appli a besoin, et calculez le reste à la demande. Par exemple, si vous construisez une liste de tâches, gardez simplement un tableau des tâches sous la main ; pas besoin d’une variable d’état pour le compteur. Au lieu de ça, quand vous voulez afficher le nombre de tâches, prenez simplement la longueur du tableau de tâches.

Pensez à toutes les données de notre application. On a :

  • La liste des produits
  • Le texte de recherche saisi par l’utilisateur
  • La valeur de la case à cocher
  • La liste filtrée des produits

Passons-les en revue pour déterminer lesquelles constituent notre état. Posez-vous simplement ces trois questions pour chaque donnée :

  1. Est-elle passée depuis un parent via les props ? Si oui, ce n’est probablement pas de l’état.
  2. Est-elle figée dans le temps ? Si oui, ce n’est probablement pas de l’état.
  3. Pouvez-vous la calculer en vous basant sur le reste de l’état ou les props de votre composant ? Si oui, ce n’est pas de l’état.

La liste des produits est passée via les props, ce n’est donc pas de l’état. Le texte de recherche et la case à cocher semblent être de l’état puisqu’ils changent avec le temps et ne peuvent être calculés à partir d’autre chose. Enfin, la liste filtrée des produits ne constitue pas de l’état puisqu’elle peut être calculée en combinant la liste originale des produits avec le texte de recherche et la valeur de la case à cocher.

Au final, notre état contient :

  • Le texte de recherche saisi par l’utilisateur
  • La valeur de la case à cocher

Étape 4 : identifier où votre état doit vivre

Voir le Pen Penser en React : Étape 4 sur CodePen.

Bon, nous avons identifié le contenu minimal de notre état applicatif. À présent, nous devons identifier quel composant modifie, ou possède, cet état.

Souvenez-vous : React se fonde sur un flux de données unidirectionnel qui descend le long de la hiérarchie des composants. Quant à savoir quel composant devrait posséder quel état, ce n’est pas forcément évident d’entrée de jeu. C’est souvent la partie la plus difficile à comprendre pour les novices, alors suivez ces étapes pour trouver la réponse :

Pour chaque partie de l’état de votre application :

  • Identifiez chaque composant qui affiche quelque chose basé sur cet état.
  • Trouvez leur plus proche ancêtre commun (un composant unique, au-dessus de tous les composants qui ont besoin de cette partie de l’état dans la hiérarchie).
  • L’ancêtre commun ou un autre composant situé plus haut dans la hiérarchie devrait posséder cette portion d’état.
  • Si vous ne trouvez pas de composant logique pour posséder cette partie de l’état, créez-en un exprès pour ça, et ajoutez-le quelque part dans la hiérarchie au-dessus de l’ancêtre commun.

Utilisons cette stratégie pour notre application :

  • ProductTable doit filtrer la liste des produits en fonction de l’état et SearchBar doit afficher l’état du texte de recherche et de la case à cocher.
  • Leur ancêtre commun est FilterableProductTable.
  • Conceptuellement, il est logique que le texte du filtre et la valeur de la case à cocher soient dans FilterableProductTable

Parfait, nous avons donc décidé que FilterableProductTable possèdera notre état. Tout d’abord, ajoutez une propriété d’instance this.state = {filterText: '', inStockOnly: false} dans le constructor de FilterableProductTable pour refléter l’état initial de votre application. Ensuite, passez filterText et inStockOnly à ProductTable et SearchBar via leurs props. Enfin, utilisez ces props pour filtrer les lignes dans ProductTable et définir les valeurs des champs du formulaire dans SearchBar.

Vous pouvez commencer à voir comment votre application se comportera : définissez filterText à "ball" et rafraîchissez votre appli. Vous verrez que le tableau de données est correctement mis à jour.

Étape 5 : ajouter le flux de données inverse

Voir le Pen Penser en React : Étape 5 sur CodePen.

Pour le moment, nous avons construit une appli qui s’affiche correctement en fonction des props et de l’état qui descendent le long de la hiérarchie. À présent, il est temps de permettre la circulation des données dans l’autre sens : les composants de formulaire situés plus bas dans la hiérarchie ont besoin de mettre à jour l’état dans FilterableProductTable.

React rend ce flux de données explicite pour faciliter la compréhension du fonctionnement de votre programme, mais cela demande un peu plus de code qu’une liaison de données bidirectionnelle classique.

Si vous essayez de saisir du texte ou de cocher la case dans la version actuelle de l’exemple, vous verrez que React ne tient pas compte de vos saisies. C’est volontaire, car nous avons spécifié l’attribut value de l’élément input pour qu’il soit toujours égal à l’état passé depuis FilterableProductTable.

Réfléchissons à ce que nous voulons qu’il se passe. Nous voulons garantir que chaque fois que l’utilisateur met à jour le formulaire, nous mettons à jour l’état pour refléter la saisie de l’utilisateur. Puisque les composants ne peuvent mettre à jour que leur propre état, FilterableProductTable passera une fonction de rappel à SearchBar, qui devra être déclenchée chaque fois que l’état doit être mis à jour. Nous pouvons utiliser l’événement onChange des champs pour cela. Les fonctions de rappel passées par FilterableProductTable appelleront setState(), et l’application sera mise à jour.

Même si ça semble compliqué, c’est juste quelques lignes de code. Et la façon dont vos données circulent dans l’appli est ainsi vraiment claire.

Et c’est tout

Avec un peu de chance, vous avez maintenant une idée de la façon de penser la construction de vos composants et applis en React. Bien que ça demande peut-être un peu plus de code que vous n’en avez l’habitude, souvenez-vous que le code est lu beaucoup plus souvent qu’il n’est écrit, et que ce type de code, modulaire et explicite, est extrêmement facile à lire. Plus vous écrirez de composants, plus vous apprécierez cette clarté et cette modularité, et avec la réutilisation du code, le nombre de vos lignes de code commencera à diminuer. 😀