b) Les modules et collections
Les modules
Introduction
Veuillez lire l'introduction aux modules en JS dans la partie 1 de ce cours : Les modules ES6.
Conventions pour le cours
A partir de maintenant, nous allons créer un fichier pour chaque composant React. Cela permet de mieux structurer le code de nos applications, de rendre le code plus maintenable.
De plus, nous allons créer un dossier par famille de composants.
Nous placerons chaque composant créé dans son propre fichiers : src/components/[ComponentName].jsx
Si un composant est lié à une famille de composants, nous le placerons dans :
- pour le composant "racine" :
src/components/[ComponentName]/index.jsx
- pour les composants liés au composant "racine" :
src/components/[ComponentName]/[ComponentName].jsx
Création de modules
Pour ce tutoriel, veuillez créer une copie du tutoriel components
, si nécessaire voici le code du tutoriel components, et l'appeler collections
. Changez le nom du projet dans package.json
.
Veuillez restructurer toute l'application pour que chaque composant React se trouve dans son propre module, au sein d'un nouveau répertoire /src/components
.
Veuillez déplacer App.css
et App.tsx
dans le dossier /src/components/App
et renommer /src/components/App/App.tsx
en /src/components/App/index.tsx
.
Ensuite, créez trois nouveaux modules & dossiers :
/src/components/Header/index.tsx
: dans ce module, ajouter le code du composantHeader
qui se trouve dans le composantApp
et n'oubliez pas d'exporterHeader
./src/components/Main/index.tsx
: dans ce module, ajouter le code du composantMain
qui se trouve dans le composantApp
et n'oubliez pas d'exporterMain
./src/components/Footer/index.tsx
: dans ce module, ajouter le code du composantFooter
qui se trouve dans le composantApp
et oubliez pas d'exporterFooter
.
Maintenant, mettez à jour le composant App
afin de supprimer la définition des composants exportés (Header
, Main
& Footer
).
Pensez également à modifier le fichier /src/main.tsx
pour mettre à jour le chemin de App
lors de l'import (import App from './components/App/index.tsx'
).
Veuillez vérifier que votre application fonctionne correctement.
A ce stade-ci, nous remarquons que le CSS pour nos nouveaux composants se trouve toujours dans App.css
. Cela fonctionne ainsi, car le CSS appliqué à un composant parent s'applique à ses enfants.
Néanmoins, si nous souhaitons réellement avoir des composants entièrement réutilisables, alors il est intéressant de créer un fichier .css
pour chaque nouveau composant :
/src/components/Header/Header.css
: veuillez y ajouter le code CSS associé qui se trouve dansApp.css
(et l'effacer deApp.css
). Dans/src/components/Header/index.tsx
, veuillez importer la nouvelle feuille de style associée :import "./Header.css";
/src/components/Main/Main.css
: veuillez y ajouter le code CSS associé qui se trouve dansApp.css
(et l'effacer deApp.css
). Dans/src/components/Main/index.tsx
, veuillez importer la nouvelle feuille de style associée :import "./Main.css";
/src/components/Footer/Footer.css
: veuillez y ajouter le code CSS associé qui se trouve dansApp.css
(et l'effacer deApp.css
). Dans/src/components/Footer/index.tsx
, veuillez importer la nouvelle feuille de style associée :import "./Footer.css";
Ainsi, si nous souhaitons réutiliser le composant Header
dans un futur projet, nous n'aurons plus qu'à copier l'entièreté du dossier /src/components/ComponentName
dans ce projet.
Les collections de composants
Très souvent nous allons vouloir générer des UI à partir de collections de données.
Par exemple, à cette étape-ci, si nous souhaitions afficher un menu de pizzas dans notre interface, nous allons souhaiter le faire sur base d'un tableau d'objets représentant des pizzas.
Voici à quoi ressemble actuellement le composant App
:
tsximport "./App.css";import Footer from "../Footer";import Header from "../Header";import Main from "../Main";function App() {return (<div className="page"><Header title="We love Pizza" version={0 + 1} /><Main /><Footer /></div>);}export default App;
Ici, nous décidons que dans la famille de composants Main
, nous souhaitons ajouter un composant PizzaMenu
qui devra afficher toutes les pizzas de la pizzeria sur base d'un array d'objets.
Voici le code du nouveau composant src/components/Main/PizzaMenu.tsx
:
tsxconst pizzas = [{id: 1,title: "4 fromages",content: "Gruyère, Sérac, Appenzel, Gorgonzola, Tomates",},{id: 2,title: "Vegan",content: "Tomates, Courgettes, Oignons, Aubergines, Poivrons",},{id: 3,title: "Vegetarian",content: "Mozarella, Tomates, Oignons, Poivrons, Champignons, Olives",},{id: 4,title: "Alpage",content: "Gruyère, Mozarella, Lardons, Tomates",},{id: 5,title: "Diable",content: "Tomates, Mozarella, Chorizo piquant, Jalapenos",},];const PizzaMenu = () => {return (<table><thead><tr><th>Pizza</th><th>Description</th></tr></thead><tbody>{pizzas.map((pizza) => (<tr><td>{pizza.title}</td><td>{pizza.content}</td></tr>))}</tbody></table>);};export default PizzaMenu;
pizzas
contient un tableau d'objets représentant des pizzas.
La fonction map
permet d'itérer sur chacun des objets de pizzas
et de générer un nouvel array d'éléments React qui seront rendus par React le moment venu.
Ici la fonction map
génère un array de <tr>
qui contient les lignes de la future table
HTML qui sera rendue par React dans le browser.
Comme le code qui génère les <tr>
est du TS/JS, il doit se trouver entre accolades.
Voici le code du composant Main
(dans /src/components/Main/index.tsx
mis à jour pour utiliser PizzaMenu
:
tsxconst Main = () => {return (<main><p>My HomePage</p><p>Because we love JS, you can also click on the header to stop / start themusic ; )</p><audio id="audioPlayer" controls autoPlay><source src={sound} type="audio/mpeg" />Your browser does not support the audio element.</audio><PizzaMenu /></main>);};
N'oubliez pas d'importer le module PizzaMenu
dans Main
.
Il est temps de voir le résultat visuel. Wow, nous avons généré dynamiquement un tableau HTML sur base d'une collection de données !
Il nous reste plus qu'à améliorer le design du menu. Pour ce faire, nous pouvons le faire à l'aide d'un nouveau fichier /src/components/Main/PizzaMenu.css
dans lequel nous allons créer une classe :
css.pizza-menu {margin: 0 auto;padding: 5px;border: 1px solid black;background-color: red;}
Cette classe devrait nous permettre de centrer le tableau, d'ajouter une bordure et d'avoir un background rouge.
Pour appliquer cette classe, il faut mettre à jour le composant PizzaMenu
en important PizzaMenu.css
et en ajoutant la classe à <table>
:
tsximport "./PizzaMenu.css";// Definition of pizzas ...const PizzaMenu = () => {return (<table className="pizza-menu">//...
Veuillez constater le résultat visuel du site de la pizzeria ! C'est pas mal !
⚡️ Veuillez jeter un œil à la console de votre browser... Il y a un sérieux message d'avertissement : "Each child in a list should have a unique "key" prop.".
L'attribut "key"
React utilise l'attribut key
d'elements React qui se trouve dans un array afin de déterminer comment mettre à jour la vue générée par un composant quand il est "re-render".
Il est donc important d'ajouter une clé. Mettons à jour le code de PizzaMenu
:
tsx{pizzas.map((pizza) => (<tr key={pizza.id}><td>{pizza.title}</td><td>{pizza.content}</td></tr>))}
Notre application ne contient plus de messages d'avertissement.
Exercice 2.3 : Les modules & collection
Nous allons continuer le projet de votre exercice précédent qui se trouve dans le dossier /exercises/2.1-2-3-4
dans votre git repo.
Notre client nous a donné une nouvelle version des données d'entrée du composant App
,ainsi qu'une idée du résultat du composant App
:
tsxconst App = () => {const pageTitle = "Informations sur les films dans les cinémas";const cinema1Name = "UGC DeBrouckère";const moviesCinema1 = [{title: "HAIKYU-THE DUMPSTER BATTLE",director: "Susumu Mitsunaka",},{title: "GOODBYE JULIA",director: "Mohamed Kordofani",},{title: "INCEPTION",director: "Christopher Nolan",},{title: "PARASITE",director: "Bong Joon-ho",},];const cinema2Name = "UGC Toison d'Or";const moviesCinema2 = [{title: "THE WATCHERS",director: "Ishana Night Shyamalan",},{title: "BAD BOYS: RIDE OR DIE",director: "Adil El Arbi, Bilall Fallah",},{title: "TENET",director: "Christopher Nolan",},{title: "THE IRISHMAN",director: "Martin Scorsese",},];return (<div><PageTitle title={pageTitle} /><Cinema name={cinema1Name} movies= {moviesCinema1} /><Cinema name={cinema2Name} movies={moviesCinema2} /></div>);};
Veuillez mettre à jour le composant Cinema
pour prendre en compte ces nouvelles props.
De plus, vous devez appliquer les conventions vues dans le cours : chaque composant React doit se trouver dans son propre module.
🤝 Tips :
- Pour définir comme type un array d'un certain type en TS, il suffit d'ajouter des crochets
[]
à la suite du type. Par exemple, pour un array deMovie
:movies: Movie[];
Une fois tout fonctionnel, veuillez faire un commit avec le message suivant : new:ex2.3
.
Exercice 2.3b : Encore une collection
Veuillez créer un nouveau projet dans le dossier /exercises/2.3b
dans votre git repo.
Nous avons reçu une application mal structurée où tout le code se trouve dans le composant App
:
tsxconst App = () => {const title = "Welcome to My App";const name1 = "Alice";const age1 = 25;const name2 = "Bob";const age2 = 30;const name3 = "Charlie";const age3 = 35;const footerText = "© 2023 My App";return (<div><h1>{title}</h1><div><h2>{name1}</h2><p>Age: {age1}</p></div><div><h2>{name2}</h2><p>Age: {age2}</p></div><div><h2>{name3}</h2><p>Age: {age3}</p></div><footer>{footerText}</footer></div>);};export default App;
Dans votre nouveau projet, vous allez maintenant commencer par copier / coller le code donné et voir que l'application fonctionne. Vous allez maintenant faire un refactor de cette application afin d'avoir un code propre et bien structuré.
Veuillez identifier les parties du code qui peuvent être transformées en composants : Le titre de la page, les cartes utilisateur et le pied de page.
Veuillez créer des fichiers pour chaque composant, importer les composants dans App
et les utiliser.
Veuillez aussi restructurer les données utilisées pour les cartes utilisateur en un tableau d'objets.
Une fois tout fonctionnel, veuillez faire un commit avec le message suivant : new:ex2.3b
.
Passer du TSX comme enfant : props.children
Parfois nous souhaitons créer un composant à partir d'autres composants.
Mais comment créer un composant de ce style là (DrinkMenu
) qui recevrait des composants enfants (DrinkCard
) ?
tsx<DrinkMenu title="Notre Menu de Boissons"><DrinkCardtitle="Coca-Cola"image="https://media.istockphoto.com/id/1289738725/fr/photo/bouteille-en-plastique-de-coke-avec-la-conception-et-le-chapeau-rouges-d%C3%A9tiquette.jpg?s=1024x1024&w=is&k=20&c=HBWfROrGDTIgD6fuvTlUq6SrwWqIC35-gceDSJ8TTP8="><p>Volume: 33cl</p><p>Prix: 2,50 €</p></DrinkCard><DrinkCardtitle="Pepsi"image="https://media.istockphoto.com/id/185268840/fr/photo/bouteille-de-cola-sur-un-fond-blanc.jpg?s=1024x1024&w=is&k=20&c=xdsxwb4bLjzuQbkT_XvVLyBZyW36GD97T1PCW0MZ4vg="><p>Volume: 33cl</p><p>Prix: 2,50 €</p></DrinkCard><DrinkCardtitle="Eau Minérale"image="https://media.istockphoto.com/id/1397515626/fr/photo/verre-deau-gazeuse-%C3%A0-boire-isol%C3%A9.jpg?s=1024x1024&w=is&k=20&c=iEjq6OL86Li4eDG5YGO59d1O3Ga1iMVc_Kj5oeIfAqk="><p>Volume: 50cl</p><p>Prix: 1,50 €</p></DrinkCard></DrinkMenu>
Nous allons créer un composant DrinkCard
qui encapsule du contenu (JSX) via props.children
. Dans /src/components/Main/DrinkCard.tsx
, veuillez ajouter :
tsxinterface DrinkCardProps {title: string;image: string;children: React.ReactNode;}const DrinkCard = (props: DrinkCardProps) => {return (<div className="drink-card"><img src={props.image} alt={props.title} className="drink-image" width="50"/><h2>{props.title}</h2><div className="drink-details">{props.children}</div></div>);};export default DrinkCard;
props.children
permet d'encapsuler des détails spécifiques sur chaque boisson (volume, prix, etc.). Nous utilisons React.ReactNode
comme type pour children
car ça peut être tout type d'élément React.
Nous allons maintenant créer un composant DrinkMenu
qui encapsule aussi du contenu (JSX) via props.children
. Dans /src/components/Main/DrinkMenu.tsx
, veuillez ajouter :
tsxinterface DrinkMenuProps {title: string;children: React.ReactNode;}const DrinkMenu = (props: DrinkMenuProps) => {return (<div className="drink-menu"><h4>{props.title}</h4><div className="drink-items">{props.children}</div></div>);};export default DrinkMenu;
props.children
va permettre d'encapsuler plusieurs composants DrinkCard
.
💭 Attention qu'ici, comme les children sont n'importe quel type d'élément React (React.ReactNode
), il serait possible d'ajouter n'importe quoi...
Comme nous codons en TS, nous pourrions tenter d'utiliser sa force de pouvoir typer de manière stricte, et n'autoriser :
- que les
children
de typeDrinkCard
s'il n'y a qu'un élément passé àDrinkMenu
- que les
children
de type array deDrinkCard
Voila comment on mettrait à jour DrinkMenuProps
:
tsximport { ReactElement } from "react";import DrinkCard from "./DrinkCard";interface DrinkMenuProps {title: string;children: ReactElement<typeof DrinkCard> | ReactElement<typeof DrinkCard>[];}
Néanmoins, en TS, tous les éléments JSX ont le type JSX.Element
. Il n'est donc pas possible de simplement utiliser le typage stricte pour impose le type des éléments de props.children
.
Nous allons maintenant utiliser les composants DrinkMenu
et DrinkCard
pour afficher un menu de boissons. Dans le composant Main
(src/components/Main/index.tsx
), veuillez ajouter :
tsx<DrinkMenu title="Notre Menu de Boissons"><DrinkCardtitle="Coca-Cola"image="https://media.istockphoto.com/id/1289738725/fr/photo/bouteille-en-plastique-de-coke-avec-la-conception-et-le-chapeau-rouges-d%C3%A9tiquette.jpg?s=1024x1024&w=is&k=20&c=HBWfROrGDTIgD6fuvTlUq6SrwWqIC35-gceDSJ8TTP8="><p>Volume: 33cl</p><p>Prix: 2,50 €</p></DrinkCard><DrinkCardtitle="Pepsi"image="https://media.istockphoto.com/id/185268840/fr/photo/bouteille-de-cola-sur-un-fond-blanc.jpg?s=1024x1024&w=is&k=20&c=xdsxwb4bLjzuQbkT_XvVLyBZyW36GD97T1PCW0MZ4vg="><p>Volume: 33cl</p><p>Prix: 2,50 €</p></DrinkCard><DrinkCardtitle="Eau Minérale"image="https://media.istockphoto.com/id/1397515626/fr/photo/verre-deau-gazeuse-%C3%A0-boire-isol%C3%A9.jpg?s=1024x1024&w=is&k=20&c=iEjq6OL86Li4eDG5YGO59d1O3Ga1iMVc_Kj5oeIfAqk="><p>Volume: 50cl</p><p>Prix: 1,50 €</p></DrinkCard></DrinkMenu>
Veuillez vous assurer que visuellement tout fonctionne.
Ca n'est pas des plus beaux, mais nous n'irons pas plus loin pour ce tutoriel-ci.
Si nécessaire, vous pouvez trouver le code associé à ce tutoriel ici : collections.
Exercice 2.4 : Utilisation de props.children & css
Nous allons continuer le projet de votre exercice précédent qui se trouve dans le dossier /exercises/2.1-2-3-4
dans votre git repo.
Veuillez créer deux nouveaux composants : Header
& Footer
.
Il doit être possible :
- d'ajouter n'importe quel type de contenu dans ces deux composants en tant qu'enfants.
- d'afficher un logo (une image) dont l'URL est à passer en propriété.
Veuillez utiliser vos nouveaux composants pour améliorer votre application web et afficher un header et un footer.
N'oubliez pas d'appliquer les conventions vues dans le cours : chaque composant React doit se trouver dans son propre module,...
Une fois tout fonctionnel, veuillez faire un commit avec le message suivant : new:ex2.4
.
🤝 Tips :
- Pour vos images, n'hésitez pas à aller sur le site https://unsplash.com/ ou https://www.istockphoto.com/.
Une fois une image trouvée, vous pouvez cliquer dessus, puis clic droit pour récupérer l'adresse du lien et l'utiliser dans votre code ; )
🍬 Challenge optionnel : peaufinage du layout de la page
S'il vous reste du temps, n'hésitez pas à peaufiner le layout de votre page et de vos composants à l'aide de CSS.
Une fois tout fonctionnel, veuillez faire un commit avec le message suivant : new:ex2.4-bonus
.
Exercice 2.4b : Encore des composants
Veuillez créer un nouveau projet ("from scratch") dans le dossier /exercises/ex2.4b
de votre git repo en utilisant les technos Vite + React + TS + SWC.
Veuillez créer un composant React permettant d'afficher des informations de base sur un utilisateur, sous forme d'une carte, sur base de ces props :
- son nom
- son âge
- si l'utilisateur est en ligne ou non
Le nom de l'utilisateur doit être affiché en tête, avec une taille de police plus grande.
L'âge doit être affiché en dessous du nom.
Si l'utilisateur est en ligne, afficher le texte "En ligne" en vert. S'il est hors ligne, afficher le texte "Hors ligne" en rouge.
Veuillez créer trois éléments de votre nouveau composant React dans le composant App
en passant à chaque fois des valeurs différentes pour les props.
N'oubliez pas d'appliquer les conventions vues dans le cours : chaque composant React doit se trouver dans son propre module,...
🤝 Tips :
- Pour faire de l'affichage conditionnel en React, vous pouvez utiliser un
ternary operator
ou unif
dans le JSX. - Par exemple, pour ajouter une classe CSS conditionnellement, vous pouvez utiliser une expression ternaire :
tsxreturn ( <div className={isOnline ? "online" : "offline"}>{isOnline ? "En ligne" : "Hors ligne"}</div> );
- Avec un
if
, vous pourriez faire la même chose, mais c'est plus long, il faut deux blocsreturn
:
tsxif (isOnline) {return <div className="online">En ligne</div>;} else {return <div className="offline">Hors ligne</div>;}
Une fois tout fonctionnel, veuillez faire un commit avec le message suivant : new:ex2.4b
.