React
Sommaire pour Apprendre React
Partie 1 : Les Fondamentaux de React
-
Introduction à React
- Qu’est-ce que React et pourquoi l’utiliser ? React est une bibliothèque JavaScript open-source développée par Facebook (maintenant Meta) pour construire des interfaces utilisateur (UI). Son objectif principal est de rendre la création d’applications web interactives, efficaces et flexibles plus simple.
Pourquoi l’utiliser ?
-
Approche basée sur les composants : Permet de diviser l’UI en petites pièces réutilisables et isolées appelées composants. Cela facilite la gestion et le développement d’applications complexes.
-
Performance (Virtual DOM) : React utilise un Virtual DOM, une représentation en mémoire du DOM réel. Lorsque le state d’un composant change, React met à jour le Virtual DOM, compare la nouvelle version avec l’ancienne, et ne met à jour que les parties nécessaires du DOM réel. Cela minimise les manipulations directes du DOM, qui sont coûteuses en performance.
-
Développement déclaratif : Vous décrivez comment l’UI devrait ressembler pour un état donné, et React s’occupe de mettre à jour l’UI lorsque le state change. Cela rend le code plus prévisible et facile à déboguer.
-
Large écosystème et communauté : React bénéficie d’un vaste écosystème d’outils, de bibliothèques (comme React Router, Redux) et d’une communauté active, ce qui facilite la résolution de problèmes et l’accès à des ressources.
-
Les avantages de React (approche basée sur les composants, Virtual DOM). Voir ci-dessus.
-
Différence entre React et un framework (bibliothèque vs framework). React est une bibliothèque (library). Elle se concentre principalement sur la couche de la vue (comment afficher les données). Vous pouvez l’intégrer dans des projets existants et choisir d’autres bibliothèques pour la gestion du state global, le routage, le fetching de données, etc. Un framework (cadre de travail), comme Angular ou Vue (dans une certaine mesure), est généralement plus complet et fournit une structure plus rigide pour l’ensemble de l’application (gestion du state, routage, etc.). Il vous impose une manière de construire votre application.
-
Mise en Place de l’Environnement de Développement Pour commencer avec React, vous aurez besoin de configurer un environnement de développement.
-
Installation de Node.js et npm/yarn/pnpm. React nécessite Node.js pour exécuter les outils de build (comme Webpack, Babel) et gérer les dépendances.
-
Téléchargez et installez Node.js depuis le site officiel (https://nodejs.org/). L’installation inclut npm (Node Package Manager).
-
Vous pouvez alternativement utiliser yarn ou pnpm comme gestionnaires de paquets, souvent préférés pour leur vitesse ou leur gestion de l’espace disque.
-
Vérifiez l’installation en exécutant
node -v
etnpm -v
(ouyarn -v
,pnpm -v
) dans votre terminal. -
Utilisation de Create React App (ou outils modernes comme Vite) pour démarrer un projet. La manière la plus rapide de démarrer une nouvelle application React est d’utiliser un “toolkit” qui configure automatiquement tout l’environnement de build.
-
Create React App (CRA) : Historiquement le choix par défaut, il configure tout sans configuration manuelle.
npx create-react-app mon-app-react cd mon-app-react npm start
-
Vite : Un outil de build plus moderne, plus rapide, qui utilise les modules ES natifs du navigateur pendant le développement. Il est souvent préféré pour les nouveaux projets.
npm create vite@latest mon-app-react --template react cd mon-app-react npm install npm run dev
-
Autres outils : Next.js (pour les applications React avec rendu côté serveur et génération de sites statiques), Remix, Parcel, etc.
-
Structure de base d’une application React. Une application créée avec CRA ou Vite a généralement une structure similaire :
mon-app-react/ ├── node_modules/ # Dépendances du projet ├── public/ # Fichiers statiques (index.html, images, etc.) ├── src/ # Le code source de votre application │ ├── index.js (ou index.jsx/ts/tsx) # Point d'entrée │ ├── App.js (ou App.jsx/ts/tsx) # Composant racine │ ├── index.css # Styles globaux │ ├── App.css # Styles du composant App │ ├── logo.svg # Exemple d'asset │ └── reportWebVitals.js # Utilitaires (si CRA) ├── .gitignore # Fichiers à ignorer par Git ├── package.json # Informations sur le projet et les dépendances └── README.md # Description du projet
Le fichier
index.js
(ou équivalent) est le point d’entrée où l’application React est rendue dans le fichierpublic/index.html
.- Présentation des outils de développement React (React Developer Tools). Ces outils sont une extension pour votre navigateur (Chrome, Firefox, Edge) qui vous permet d’inspecter l’arbre des composants React, de voir les props et le state de chaque composant, et de débugger votre application plus facilement. C’est un outil indispensable pour le développement React. Installez-le depuis le store d’extensions de votre navigateur.
-
-
JSX : Syntaxe et Utilisation
-
Qu’est-ce que JSX ? (JavaScript XML). JSX est une extension de syntaxe pour JavaScript. Il a été créé pour React et permet d’écrire du code qui ressemble à du HTML ou du XML directement dans vos fichiers JavaScript. Bien que facultatif, JSX est fortement recommandé car il simplifie l’écriture du code UI.
-
Écrire du balisage avec JSX. Au lieu d’utiliser
React.createElement()
, vous pouvez écrire des éléments React directement en utilisant une syntaxe similaire au HTML :
const element = <h1>Bonjour, monde !</h1>;
- Expressions JavaScript dans JSX (accolades
{}
). Vous pouvez intégrer n’importe quelle expression JavaScript valide à l’intérieur de accolades{}
dans JSX. Cela vous permet d’afficher des variables, d’appeler des fonctions, ou d’effectuer des calculs.
const nom = "Alice"; const element = <p>Bonjour, {nom} !</p>; function formaterNom(utilisateur) { return utilisateur.prenom + ' ' + utilisateur.nom; } const utilisateur = { prenom: 'Bob', nom: 'Dupont' }; const element = ( <h1> Bonjour, {formaterNom(utilisateur)} ! </h1> );
- Attributs et props en JSX. En JSX, vous utilisez des attributs pour configurer les éléments, tout comme en HTML, mais avec quelques différences :
- Les noms d’attributs en camelCase pour la plupart (ex:
className
au lieu declass
,htmlFor
au lieu defor
). - Les attributs acceptent des expressions JavaScript entre accolades (ex:
<img src={utilisateur.avatarUrl} />
). - Les props sont des données passées aux composants personnalisés (voir section 5).
// Attributs HTML const element = <a href="https://reactjs.org"> Lien vers React </a>; // Attributs en camelCase const elementAvecClasse = <div className="ma-classe"> Contenu </div>; // Attributs avec expressions JS const imageUrl = 'chemin/vers/image.jpg'; const imageElement = <img src={imageUrl} alt="Une image" />;
- Différences avec le HTML classique.
- Utilisation de
className
au lieu declass
pour spécifier des classes CSS. - Utilisation de
htmlFor
au lieu defor
pour les labels. - Les événements sont nommés en camelCase (ex:
onClick
,onChange
) et la valeur est une fonction, pas une chaîne de caractères. - Les styles en ligne sont passés comme un objet JavaScript (ex:
<div style={{ color: 'blue' }}>…</div>
). - Les éléments doivent être fermés, y compris les balises auto-fermantes comme
<img>
ou<input>
(ex:<img />
,<input type="text" />
). - Un composant doit retourner un seul élément racine (ou utiliser un Fragment
<></>
).
-
-
Les Composants
-
Qu’est-ce qu’un composant React ? Les composants sont les blocs de construction fondamentaux des applications React. Un composant est une fonction ou une classe JavaScript qui retourne des éléments React décrivant une partie de l’interface utilisateur. Ils permettent de diviser une UI complexe en pièces isolées, réutilisables et faciles à gérer.
-
Composants fonctionnels (et la transition des classes vers les fonctions). Les composants fonctionnels sont des fonctions JavaScript qui acceptent un seul argument (un objet
props
) et retournent des éléments React. Avec l’introduction des Hooks, les composants fonctionnels sont devenus la manière privilégiée d’écrire des composants, car ils peuvent gérer le state et les effets de bord sans avoir recours aux classes.
// Composant fonctionnel simple function Salutation(props) { return <h1>Bonjour, {props.nom} !</h1>; }
La transition des composants de classe vers les composants fonctionnels avec Hooks a simplifié beaucoup de concepts et rendu le code plus lisible.
- Composants de classe (introduction et différences avec les fonctions).
Les composants de classe sont des classes JavaScript qui étendent
React.Component
et doivent contenir une méthoderender()
qui retourne des éléments React. Ils géraient le state interne (this.state
) et le cycle de vie via des méthodes spécifiques (componentDidMount
,componentDidUpdate
,componentWillUnmount
). Ils sont moins utilisés dans le développement moderne depuis l’avènement des Hooks.
// Composant de classe simple import React from 'react'; class SalutationClass extends React.Component { render() { return <h1>Bonjour, {this.props.nom} !</h1>; } }
La principale différence réside dans la syntaxe et la manière de gérer le state et le cycle de vie (Hooks pour les fonctions vs méthodes de classe).
- Créer et importer/exporter des composants.
Les composants sont généralement définis dans des fichiers séparés (un fichier par composant est une bonne pratique). Vous utilisez
export default
pour exporter le composant principal du fichier etimport
pour l’utiliser dans un autre fichier.
// Dans Salutation.js import React from 'react'; function Salutation(props) { return <h1>Bonjour, {props.nom} !</h1>; } export default Salutation; // Dans App.js import React from 'react'; import Salutation from './Salutation'; // Chemin relatif function App() { return ( <div> <Salutation nom="Alice" /> <Salutation nom="Bob" /> </div> ); } export default App;
- Imbrication de composants. Vous pouvez utiliser des composants à l’intérieur d’autres composants pour construire des interfaces complexes. C’est le principe fondamental de la composition dans React.
// Dans Bouton.js import React from 'react'; function Bouton(props) { return <button onClick={props.onClick}>{props.texte}</button>; } export default Bouton; // Dans Toolbar.js import React from 'react'; import Bouton from './Bouton'; function Toolbar() { function handleClick() { alert('Bouton cliqué !'); } return ( <div> <Bouton texte="Action" onClick={handleClick} /> </div> ); } export default Toolbar;
Ici, le composant
Toolbar
imbrique le composantBouton
. -
-
Props : Passage de Données
-
Qu’est-ce que les props ? “Props” est l’abréviation de “properties”. Les props sont un mécanisme permettant de passer des données d’un composant parent à un composant enfant. Elles sont essentielles pour rendre les composants dynamiques et réutilisables. Les props sont passées aux composants en utilisant une syntaxe similaire aux attributs HTML/JSX.
-
Passer des données de parent à enfant via les props. Vous passez des props à un composant enfant en l’utilisant dans le JSX du parent et en ajoutant des attributs :
// Composant Parent function Parent() { const message = "Salut de la part du parent !"; return <Enfant texte={message} />; } // Composant Enfant function Enfant(props) { return <p>{props.texte}</p>; }
Dans cet exemple, le composant
Parent
passe la chaîne de caractères"Salut de la part du parent !"
au composantEnfant
via la prop nomméetexte
. Le composantEnfant
reçoit ces données dans un objetprops
(ici,{ texte: "Salut de la part du parent !" }
) et les utilise.-
Les props sont en lecture seule (unidirectionnel). Une règle fondamentale de React est que les props sont en lecture seule pour le composant qui les reçoit. Un composant ne doit jamais modifier les props qu’il reçoit. Ce flux de données unidirectionnel (“one-way data binding”) est l’une des clés de la prévisibilité et de la facilité de débogage des applications React. Si les données doivent changer, c’est le composant parent qui doit gérer ce changement (généralement via son state) et passer les nouvelles props à l’enfant.
-
Destructuration des props. Il est très courant d’utiliser la destructuration d’objets JavaScript pour extraire les valeurs des props directement dans les paramètres de la fonction du composant fonctionnel. Cela rend le code plus concis.
// Composant Enfant utilisant la destructuration function Enfant({ texte }) { return <p>{texte}</p>; }
C’est l’équivalent de
const { texte } = props;
à l’intérieur de la fonction.props.children
. La prop spécialeprops.children
représente le contenu qui est passé entre les balises d’ouverture et de fermeture d’un composant. Cela est utile pour créer des composants génériques qui “enveloppent” d’autres éléments.
// Composant Card function Card(props) { return ( <div style={{ border: '1px solid black', padding: '10px' }}> {props.children} {/* Affiche le contenu passé entre les balises <Card> */} </div> ); } // Utilisation du composant Card function App() { return ( <Card> <h2>Titre de la carte</h2> <p>Contenu de la carte.</p> </Card> ); }
Ici,
props.children
dans le composantCard
sera l’ensemble des éléments<h2>
et<p>
.- PropTypes pour la validation des props. PropTypes est une bibliothèque (souvent utilisée avec React) qui permet de définir les types de données attendus pour chaque prop d’un composant et de les marquer comme requises ou non. Cela aide à prévenir les erreurs en développement en affichant des avertissements dans la console si les props reçues ne correspondent pas aux types définis.
import PropTypes from 'prop-types'; function Greeting(props) { return <h1>Bonjour, {props.name}</h1>; } Greeting.propTypes = { name: PropTypes.string.isRequired // La prop 'name' doit être une chaîne et est requise };
Avec TypeScript, cette validation se fait au niveau du type checking pendant le développement et la compilation.
-
-
State : Gérer les Données Variables
-
Qu’est-ce que le state local d’un composant ? Alors que les props permettent de passer des données d’un parent à un enfant, le state local permet à un composant de gérer ses propres données internes qui peuvent changer au fil du temps en raison des interactions de l’utilisateur, des réponses du serveur, etc. Quand le state d’un composant change, React re-rend le composant et ses enfants descendants pour refléter les nouvelles données.
-
Utilisation du Hook
useState
(pour les composants fonctionnels). Dans les composants fonctionnels, le HookuseState
est la manière standard de déclarer et de gérer le state local. Il retourne une paire de valeurs : l’état actuel et une fonction pour le mettre à jour.
import React, { useState } from 'react'; function Compteur() { // Déclare une variable d'état 'count' et sa fonction de mise à jour 'setCount' const [count, setCount] = useState(0); // L'état initial est 0 return ( <div> <p>Le compteur est à : {count}</p> <button onClick={() => setCount(count + 1)}> Incrémenter </button> </div> ); }
-
useState(0)
: Initialise le statecount
à 0. -
[count, setCount]
:count
est la variable qui détient la valeur actuelle du state.setCount
est la fonction que vous devez appeler pour mettre à jour le statecount
. -
setCount(count + 1)
: Appeler cette fonction avec une nouvelle valeur déclenche un re-rendu du composant avec la nouvelle valeur decount
. -
Déclarer et mettre à jour le state. Comme vu ci-dessus avec
useState
, vous déclarez le state avec une valeur initiale et vous le mettez à jour en utilisant la fonction de mise à jour fournie paruseState
. -
Le state est asynchrone (comprendre la mise à jour). Les mises à jour de state via
setCount
(outhis.setState
dans les classes) peuvent être asynchrones. React peut regrouper plusieurs appels àsetCount
dans une seule mise à jour pour optimiser les performances. Cela signifie que si vous avez besoin de mettre à jour le state basé sur sa valeur précédente, il est préférable de passer une fonction à la fonction de mise à jour :
setCount(prevCount => prevCount + 1); // Utilise la valeur précédente du state
Cette forme fonctionnelle garantit que vous travaillez toujours avec la valeur la plus récente du state.
- Partage de state entre composants (remontée de state). Les données de state sont locales au composant où elles sont déclarées. Si plusieurs composants doivent accéder ou modifier le même state, ce state doit être “remonté” (lifted up) vers le plus proche ancêtre commun de ces composants. Cet ancêtre gérera le state et passera les données via des props aux composants enfants, et passera également des fonctions (via des props) aux enfants pour leur permettre de demander des mises à jour du state.
// Composant Parent qui gère le state partagé import React, { useState } from 'react'; import TemperatureInput from './TemperatureInput'; import FahrenheitDisplay from './FahrenheitDisplay'; function Calculator() { const [celsius, setCelsius] = useState(''); // State remonté // Fonction passée aux enfants pour mettre à jour le state const handleCelsiusChange = (newCelsius) => { setCelsius(newCelsius); }; return ( <div> <TemperatureInput temperature={celsius} onTemperatureChange={handleCelsiusChange} /> <FahrenheitDisplay celsius={celsius} /> </div> ); } // Composant enfant qui reçoit les données et une fonction de mise à jour function TemperatureInput({ temperature, onTemperatureChange }) { const handleChange = (e) => { onTemperatureChange(e.target.value); // Appelle la fonction passée par le parent }; return ( <fieldset> <legend>Entrez la température en Celsius :</legend> <input value={temperature} onChange={handleChange} /> </fieldset> ); } // Autre composant enfant qui reçoit les données function FahrenheitDisplay({ celsius }) { const fahrenheit = (celsius * 9 / 5) + 32; return <p>Température en Fahrenheit : {isNaN(fahrenheit) ? '' : fahrenheit}</p>; }
Dans cet exemple, le state
celsius
est géré par le composantCalculator
(l’ancêtre commun). Il passecelsius
comme prop àTemperatureInput
etFahrenheitDisplay
, et il passe également la fonctionhandleCelsiusChange
comme proponTemperatureChange
àTemperatureInput
pour permettre à ce dernier de demander la mise à jour du state. -
-
Gérer les Événements
-
Gestion des événements synthétiques de React. React utilise un système d’événements synthétiques (SyntheticEvent) qui est une surcouche des événements natifs du navigateur. Ce système assure une cohérence de comportement des événements à travers différents navigateurs et permet à React de gérer les événements de manière performante. L’objet
SyntheticEvent
que vous recevez dans vos gestionnaires est un wrapper autour de l’événement natif du navigateur. -
Attacher des gestionnaires d’événements aux éléments JSX (
onClick
,onChange
, etc.). Vous attachez des gestionnaires d’événements directement sur les éléments JSX en utilisant des noms de props camelCase (ex:onClick
,onChange
,onSubmit
,onMouseEnter
). La valeur de ces props est la fonction JavaScript que vous voulez exécuter lorsque l’événement se produit.
function MaFenetre() { function handleClick() { alert('Le bouton a été cliqué.'); } return ( <button onClick={handleClick}> Cliquez-moi </button> ); }
Notez que vous passez la fonction elle-même (
handleClick
), et non le résultat de l’appel de la fonction (handleClick()
).- Passer des arguments aux gestionnaires d’événements.
Parfois, vous devez passer des arguments supplémentaires à votre gestionnaire d’événements. Vous pouvez le faire en utilisant une fonction fléchée ou
bind
. L’approche avec la fonction fléchée est généralement plus simple.
// Utilisation d'une fonction fléchée (recommandé) function ListeItems() { const items = ['Pomme', 'Banane', 'Cerise']; function handleClick(item) { alert('Vous avez cliqué sur : ' + item); } return ( <ul> {items.map((item, index) => ( // La fonction fléchée appelle handleClick avec l'item spécifique <li key={index} onClick={() => handleClick(item)}> {item} </li> ))} </ul> ); } // Utilisation de .bind() (moins courant avec les Hooks) function BoutonAvecId({ id }) { function handleClick(id, event) { console.log('Bouton avec ID', id, 'cliqué. Événement:', event); } // Utilise bind pour lier 'this' (si nécessaire) et passer l'id comme premier argument return ( <button onClick={handleClick.bind(this, id)}> Cliquez Bouton {id} </button> ); }
Lorsque vous utilisez une fonction fléchée, l’événement
SyntheticEvent
est automatiquement passé comme dernier argument si vous le définissez dans la fonction fléchée. -
-
Rendu Conditionnel
-
Afficher des éléments différemment selon des conditions. Le rendu conditionnel dans React consiste à afficher des éléments différents ou à modifier l’apparence d’un élément en fonction de certaines conditions (
if
,else
, état, props, etc.). -
Utilisation des opérateurs
if
,&&
,? :
en JSX. Vous pouvez utiliser des expressions JavaScript standard pour le rendu conditionnel à l’intérieur de vos composants et en JSX.
Avec
if
/else
(à l’extérieur du JSX):function Salutation(props) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn) { return <h1>Bienvenue de retour !</h1>; } else { return <h1>Veuillez vous connecter.</h1>; } }
Cette approche est utile pour les blocs de logique plus importants.
Avec l’opérateur logique
&&
(pour inclure ou exclure un élément): Si la condition esttrue
, l’élément après&&
est rendu. Si la condition estfalse
, React ignore l’élément.function Messages({ unreadMessages }) { return ( <div> <h1>Bonjour !</h1> {unreadMessages.length > 0 && <h2> Vous avez {unreadMessages.length} messages non lus. </h2> } </div> ); }
Avec l’opérateur ternaire
? :
(pour choisir entre deux éléments): Utile lorsque vous avez deux chemins de rendu possibles (true
oufalse
).function LoginControl(props) { const isLoggedIn = props.isLoggedIn; return ( <div> {isLoggedIn ? ( <button>Déconnexion</button> ) : ( <button>Connexion</button> )} </div> ); }
-
-
Affichage de Listes et Clés (
key
)- Rendre des listes d’éléments (tableaux de données).
Dans React, vous pouvez afficher des listes d’éléments en utilisant les fonctions de manipulation de tableaux JavaScript (comme
map()
) pour transformer un tableau de données en un tableau d’éléments React.
function ListeFruits() { const fruits = ['Pomme', 'Banane', 'Cerise']; return ( <ul> {fruits.map((fruit) => ( <li>{fruit}</li> ))} </ul> ); }
La fonction
map()
itère sur le tableaufruits
et retourne un nouveau tableau d’éléments<li>
pour chaque fruit.-
L’importance de la prop
key
lors du rendu de listes. Lorsque vous rendez des listes d’éléments en React, vous devez toujours inclure une propkey
unique pour chaque élément de la liste. React utilise leskey
pour identifier quels éléments ont changé, sont ajoutés, ou sont supprimés. Cela aide React à mettre à jour efficacement l’interface utilisateur lors des re-rendus, en minimisant la création ou la destruction inutile d’éléments du DOM. -
Pourquoi les clés sont nécessaires et comment les choisir. Sans clés, React aurait du mal à identifier de manière unique chaque élément de la liste. Par exemple, si vous ajoutiez un nouvel élément au milieu de la liste, sans clés, React pourrait avoir besoin de re-rendre toute la liste à partir de ce point. Avec des clés stables et uniques, React peut identifier précisément l’élément inséré et ne mettre à jour que cette partie.
Comment choisir une clé :
- Utilisez un ID stable et unique : La meilleure clé est un ID unique provenant de vos données (par exemple, un ID de base de données).
- Évitez d’utiliser l’index du tableau comme clé : L’index ne doit être utilisé comme clé qu’en dernier recours, et uniquement si la liste ne changera jamais d’ordre, ne sera jamais filtrée, ou ne comportera jamais de nouveaux éléments ajoutés ou retirés. Si la liste peut être modifiée, utiliser l’index comme clé peut entraîner des problèmes de performance et des bugs potentiels (car l’index change lorsque l’ordre ou le contenu de la liste change).
function ListeProduits({ produits }) { return ( <ul> {produits.map((produit) => ( // Utilisation de produit.id comme clé unique et stable <li key={produit.id}> {produit.nom} - {produit.prix} € </li> ))} </ul> ); }
Assurez-vous que la valeur de la clé est une chaîne de caractères ou un nombre.
- Rendre des listes d’éléments (tableaux de données).
Dans React, vous pouvez afficher des listes d’éléments en utilisant les fonctions de manipulation de tableaux JavaScript (comme
Partie 2 : Hooks et Concepts Intermédiaires
10. Les Hooks de Base (Approfondissement)
-
useEffect
: Gérer les effets de bord (appels API, interactions DOM, timers). Le HookuseEffect
vous permet de réaliser des “effets de bord” (side effects) dans les composants fonctionnels. Les effets de bord sont des opérations qui interagissent avec le monde extérieur à React, comme les appels réseau (fetching de données), la manipulation directe du DOM, les abonnements, les timers, la configuration d’écouteurs d’événements globaux, etc. -
Montage, mise à jour et démontage avec
useEffect
.useEffect
s’exécute après chaque rendu du composant par défaut. Vous pouvez contrôler quand l’effet s’exécute en utilisant le tableau de dépendances. -
Montage (Mounting) : L’effet s’exécute après le premier rendu. Si le tableau de dépendances est vide (
[]
), l’effet ne s’exécute qu’une seule fois, au montage. -
Mise à jour (Updating) : L’effet s’exécute après chaque rendu si le tableau de dépendances n’est pas spécifié, ou si les valeurs dans le tableau de dépendances ont changé par rapport au rendu précédent.
-
Démontage (Unmounting) : Vous pouvez retourner une fonction de nettoyage depuis
useEffect
. Cette fonction est exécutée lorsque le composant est démonté, ou avant que l’effet ne s’exécute à nouveau (lors d’une mise à jour), pour nettoyer les ressources (annuler des abonnements, clearTimeout, etc.). -
Le tableau de dépendances de
useEffect
. Le deuxième argument deuseEffect
est un tableau de valeurs dont dépend l’effet. -
S’il est omis : L’effet s’exécute après chaque rendu.
-
S’il est un tableau vide (
[]
) : L’effet s’exécute une seule fois après le montage et la fonction de nettoyage (si présente) s’exécute au démontage. Utile pour les configurations uniques ou les abonnements qui ne changent pas. -
S’il contient des valeurs (
[prop1, state2]
) : L’effet s’exécute après le montage et après chaque rendu où l’une des valeurs du tableau a changé.
import React, { useState, useEffect } from 'react';
function ExempleEffect({ userId }) {
const [data, setData] = useState(null);
useEffect(() => {
// Cet effet s'exécute à chaque fois que userId change
console.log('Fetching data for user:', userId);
fetch(`https://api.example.com/users/${userId}`)
.then(response => response.json())
.then(data => setData(data));
// Fonction de nettoyage (s'exécute avant le prochain effet ou au démontage)
return () => {
console.log('Cleaning up effect for user:', userId);
// Ici, vous pourriez annuler la requête ou nettoyer un abonnement
};
}, [userId]); // Dépendance sur userId
useEffect(() => {
// Cet effet s'exécute une seule fois au montage
console.log('Component mounted.');
return () => {
console.log('Component unmounted.');
};
}, []); // Tableau de dépendances vide
return (
<div>
<h2>Détails de l'utilisateur</h2>
{data ? <p>{data.name}</p> : <p>Chargement...</p>}
</div>
);
}
* `useContext` : Partager des données sans passer les props manuellement à chaque niveau.
Le Hook `useContext` vous permet de vous abonner au contexte React le plus proche sans avoir à utiliser un composant `Context.Consumer`. Le Context API est conçu pour partager des données qui peuvent être considérées comme "globales" pour un arbre de composants (comme le thème actuel, la langue de l'utilisateur, les informations d'authentification, etc.) sans devoir passer des props manuellement à travers chaque niveau de l'arbre (props drilling).
-
Créer, fournir et consommer un contexte.
-
Créer un contexte : Utilisez
React.createContext()
.// Dans ThemeContext.js import React from 'react'; const ThemeContext = React.createContext('light'); // 'light' est la valeur par défaut export default ThemeContext;
-
Fournir une valeur de contexte : Utilisez le
.Provider
du contexte créé plus haut dans l’arbre des composants.// Dans App.js import React, { useState } from 'react'; import ThemeContext from './ThemeContext'; import Toolbar from './Toolbar'; function App() { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={theme}> <Toolbar /> <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> Changer de thème </button> </ThemeContext.Provider> ); }
-
Consommer le contexte : Utilisez le Hook
useContext
dans un composant fonctionnel.// Dans Toolbar.js import React, { useContext } from 'react'; import ThemeContext from './ThemeContext'; function Toolbar() { const theme = useContext(ThemeContext); // Lit la valeur du contexte return ( <div style={{ background: theme === 'dark' ? 'black' : 'white', color: theme === 'dark' ? 'white' : 'black' }}> Thème actuel : {theme} </div> ); }
useRef
: Accéder aux éléments du DOM ou conserver des valeurs mutables sans provoquer de re-rendu. Le HookuseRef
retourne un objet ref mutable dont la propriété.current
est initialisée avec l’argument passé (initialValue
). L’objet retourné persistera pendant toute la durée de vie du composant.useRef
est utile pour deux cas principaux :- Accéder aux éléments du DOM : Il permet d’obtenir une référence directe à un nœud du DOM rendu par React.
- Stocker une valeur mutable qui ne provoque pas de re-rendu : Contrairement au state, la modification de
.current
d’une ref ne déclenche pas de re-rendu du composant. C’est utile pour stocker des valeurs qui doivent persister entre les rendus mais dont les changements n’ont pas besoin de mettre à jour l’UI (ex: identifiants de timers, compteurs qui ne s’affichent pas, valeurs précédentes de props ou state).
import React, { useRef, useEffect } from 'react'; function InputFocus() { // Crée une ref pour stocker la référence à l'élément input const inputRef = useRef(null); useEffect(() => { // Accède à l'élément DOM via inputRef.current et lui donne le focus après le montage inputRef.current.focus(); }, []); // Tableau de dépendances vide : s'exécute une seule fois au montage return ( <div> <input ref={inputRef} type="text" /> {/* Associe la ref à l'élément input */} </div> ); } function CompteurSansRerendu() { const compteurRef = useRef(0); const handleClick = () => { compteurRef.current += 1; // Modifie la valeur sans re-rendu console.log('Compteur (ref) :', compteurRef.current); // Si on voulait afficher la valeur, il faudrait aussi utiliser useState }; return ( <button onClick={handleClick}> Incrementer le compteur (ref) </button> ); }
-
-
Hooks Avancés
useReducer
: Gérer un state complexe avec une logique de mise à jour définie. Le HookuseReducer
est souvent une alternative àuseState
lorsque vous avez une logique de state plus complexe impliquant plusieurs sous-valeurs ou lorsque le prochain état dépend de l’état précédent. Il est inspiré de Redux et suit le modèle d’un “reducer”. Il prend un “reducer” (une fonction qui spécifie comment le state change en réponse à des “actions”) et un état initial, et retourne l’état actuel ainsi qu’une fonctiondispatch
pour déclencher des actions.
import React, { useReducer } from 'react'; // Le Reducer : prend l'état actuel et une action, retourne le nouvel état function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; case 'reset': return { count: action.payload }; // Utilise une charge utile pour le reset default: throw new Error(); // Gérer les actions inconnues } } // Composant utilisant useReducer function CompteurReducer() { // useReducer retourne l'état actuel et la fonction dispatch // Le premier argument est la fonction reducer, le second est l'état initial const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( <div> <p>Compteur : {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>+1</button> <button onClick={() => dispatch({ type: 'decrement' })}>-1</button> <button onClick={() => dispatch({ type: 'reset', payload: 0 })}>Reset</button> </div> ); }
useReducer
peut rendre le code plus prévisible et plus facile à tester lorsque la logique de mise à jour du state devient non triviale.useCallback
: Mémoriser des fonctions pour optimiser les performances. Le HookuseCallback
retourne une version mémorisée (cached) de la fonction de rappel qui ne change que si l’une des dépendances a changé. Cela est utile lors du passage de fonctions de rappel à des composants enfants optimisés (React.memo
) pour éviter des re-rendus inutiles du composant enfant lorsque la fonction de rappel est recréée à chaque rendu du parent.
import React, { useState, useCallback } from 'react'; import Bouton from './Bouton'; // Supposons que Bouton est un composant optimisé avec React.memo function ParentAvecCallback() { const [count, setCount] = useState(0); // La fonction handleClick est mémorisée. Elle ne sera recréée que si 'count' change. const handleClick = useCallback(() => { setCount(count + 1); }, [count]); // Dépendance sur 'count' return ( <div> <p>Compteur : {count}</p> {/* Bouton reçoit la même instance de handleClick tant que count ne change pas */} <Bouton onClick={handleClick}>Incrémenter</Bouton> </div> ); } // Dans Bouton.js (pour que useCallback soit efficace) import React from 'react'; const Bouton = React.memo(({ onClick, children }) => { console.log('Rendu de Bouton'); // Ne s'affiche que si les props changent return <button onClick={onClick}>{children}</button>; }); export default Bouton;
Sans
useCallback
, la fonctionhandleClick
serait recréée à chaque rendu deParentAvecCallback
, provoquant un re-rendu deBouton
même si ses “données” n’ont pas changé.useMemo
: Mémoriser des valeurs calculées pour optimiser les performances. Le HookuseMemo
retourne une valeur mémorisée. Il calcule une valeur coûteuse uniquement lorsque l’une des dépendances a changé. Cela peut éviter des calculs coûteux lors de chaque rendu.
import React, { useState, useMemo } from 'react'; function ListeComplexe({ items, filtre }) { const [count, setCount] = useState(0); // Le calcul du résultat filtré est mémorisé. Il ne se refait que si 'items' ou 'filtre' changent. const filteredItems = useMemo(() => { console.log('Filtrage de la liste...'); // Ne s'affiche que lors d'un re-calcul return items.filter(item => item.includes(filtre)); }, [items, filtre]); // Dépendances : items et filtre return ( <div> <p>Compteur (non lié au filtre) : {count}</p> <button onClick={() => setCount(count + 1)}>Incrémenter le compteur</button> <ul> {filteredItems.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> </div> ); }
Sans
useMemo
, la fonctionfilter
s’exécuterait à chaque rendu deListeComplexe
, même si seul lecount
changeait.- Créer des Hooks personnalisés pour réutiliser de la logique avec état.
Un Hook personnalisé est une fonction JavaScript dont le nom commence par “use” et qui peut appeler d’autres Hooks (comme
useState
,useEffect
,useContext
, etc.). Les Hooks personnalisés vous permettent d’extraire la logique d’un composant (comme le fetching de données, la gestion de l’état d’un formulaire, la gestion d’un abonnement) dans une fonction réutilisable à travers différents composants. C’est une manière très puissante de partager de la logique sans utiliser de composants de rendu (render props
) ou de composants d’ordre supérieur (higher-order components
).
// Dans useFetch.js (Exemple de Hook personnalisé pour le fetching de données) import { useState, useEffect } from 'react'; function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const abortController = new AbortController(); const signal = abortController.signal; setLoading(true); fetch(url, { signal }) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { setData(data); setError(null); }) .catch(error => { if (error.name !== 'AbortError') { setError(error); } }) .finally(() => { setLoading(false); }); // Fonction de nettoyage pour annuler la requête si le composant est démonté return () => { abortController.abort(); }; }, [url]); // Dépendance sur l'URL return { data, loading, error }; } export default useFetch; // Utilisation du Hook personnalisé dans un composant import React from 'react'; import useFetch from './useFetch'; function UserProfile({ userId }) { const { data: user, loading, error } = useFetch(`https://api.example.com/users/${userId}`); if (loading) return <p>Chargement du profil...</p>; if (error) return <p>Erreur lors du chargement : {error.message}</p>; if (!user) return null; // ou un état vide return ( <div> <h2>{user.name}</h2> <p>Email: {user.email}</p> </div> ); }
Le Hook
useFetch
encapsule toute la logique de fetching, de gestion des états (chargement, erreur) et de nettoyage. Le composantUserProfile
peut ensuite simplement utiliser ce Hook pour obtenir les données, ce qui rend le composant plus simple et la logique réutilisable. -
Gestion des Formulaires En React, la gestion des formulaires diffère de la manière traditionnelle du HTML car React préfère que l’état des éléments de formulaire (comme la valeur d’un champ de texte ou si une case à cocher est cochée) soit géré par le state du composant.
- Formulaires contrôlés (gérés par le state de React).
Un “formulaire contrôlé” est un élément de formulaire dont la valeur ou l’état est contrôlé par le state de React. Lorsque l’utilisateur saisit des données, un gestionnaire d’événements (
onChange
) est déclenché pour mettre à jour le state du composant, et la valeur du champ est définie par le state. Cela crée un flux de données unidirectionnel où l’état de React est la source de vérité.
import React, { useState } from 'react'; function FormulaireControle() { const [nom, setNom] = useState(''); // Le state contrôle la valeur de l'input const handleChange = (event) => { setNom(event.target.value); // Met à jour le state lorsque l'input change }; const handleSubmit = (event) => { alert('Le nom soumis est : ' + nom); event.preventDefault(); // Empêche le rechargement de la page par défaut du formulaire HTML }; return ( <form onSubmit={handleSubmit}> <label> Nom : <input type="text" value={nom} onChange={handleChange} /> {/* value est lié au state */} </label> <button type="submit">Soumettre</button> </form> ); }
Dans cet exemple, la valeur de l’input text est toujours égale à la valeur du state
nom
. Chaque frappe déclenchehandleChange
, qui met à jour le state, ce qui entraîne un re-rendu du composant et met à jour la valeur affichée dans l’input.- Gérer les champs de saisie (texte, checkboxes, radios, select).
La gestion des autres types d’éléments de formulaire suit un principe similaire, en utilisant le state et le gestionnaire
onChange
, mais en accédant à l’état de l’élément via différentes propriétés de l’objetevent.target
: <input type="text">
,<textarea>
: Utilisezevent.target.value
.<input type="checkbox">
: Utilisezevent.target.checked
(un booléen).<input type="radio">
: Similaire aux checkboxes, utilisezevent.target.checked
etevent.target.value
pour identifier quel bouton est sélectionné.<select>
: Utilisezevent.target.value
pour la valeur sélectionnée (ouevent.target.value
avec l’attributmultiple
pour les sélections multiples, bien que cela soit souvent géré différemment dans des cas plus complexes).
import React, { useState } from 'react'; function FormulaireTypes() { const [estInscrit, setEstInscrit] = useState(false); const [genre, setGenre] = useState('homme'); // Valeur par défaut pour radio const [fruitPrefere, setFruitPrefere] = useState('banane'); // Valeur par défaut pour select const handleCheckboxChange = (event) => { setEstInscrit(event.target.checked); }; const handleRadioChange = (event) => { setGenre(event.target.value); }; const handleSelectChange = (event) => { setFruitPrefere(event.target.value); }; return ( <form> <label> <input type="checkbox" checked={estInscrit} onChange={handleCheckboxChange} /> Je souhaite m'inscrire </label> <br/> <label> Genre : <input type="radio" value="homme" checked={genre === 'homme'} onChange={handleRadioChange} /> Homme <input type="radio" value="femme" checked={genre === 'femme'} onChange={handleRadioChange} /> Femme </label> <br/> <label> Fruit préféré : <select value={fruitPrefere} onChange={handleSelectChange}> <option value="pomme">Pomme</option> <option value="banane">Banane</option> <option value="cerise">Cerise</option> </select> </label> </form> ); }
- Validation de formulaires.
La validation des formulaires en React se fait généralement en ajoutant de la logique dans le gestionnaire
onSubmit
ou dans le gestionnaireonChange
pour afficher des messages d’erreur à l’utilisateur si les données saisies ne sont pas valides. Vous pouvez stocker les messages d’erreur dans le state du composant. Pour des validations plus complexes, il existe des bibliothèques comme Formik ou React Hook Form, souvent couplées à des bibliothèques de validation comme Yup ou Zod.
import React, { useState } from 'react'; function FormulaireValidation() { const [email, setEmail] = useState(''); const [erreurEmail, setErreurEmail] = useState(''); const handleEmailChange = (event) => { const newEmail = event.target.value; setEmail(newEmail); // Simple validation if (newEmail.length > 0 && !newEmail.includes('@')) { setErreurEmail('L\'email doit contenir un @.'); } else { setErreurEmail(''); // Effacer l'erreur si valide ou vide } }; const handleSubmit = (event) => { event.preventDefault(); // Re-valider avant soumission if (!email.includes('@')) { setErreurEmail('Veuillez entrer un email valide.'); return; // Empêcher la soumission si invalide } alert('Formulaire soumis avec email : ' + email); // Logique pour envoyer les données... }; return ( <form onSubmit={handleSubmit}> <label> Email : <input type="text" value={email} onChange={handleEmailChange} /> </label> {erreurEmail && <p style={{ color: 'red' }}>{erreurEmail}</p>} {/* Afficher l'erreur */} <button type="submit">Soumettre</button> </form> ); }
Les formulaires “non contrôlés” existent également (où le DOM gère l’état de l’input), mais ils sont moins idiomatiques en React et sont généralement utilisés pour des cas simples ou pour l’intégration avec du code JavaScript non-React. L’accès à leur valeur se fait via une ref.
- Formulaires contrôlés (gérés par le state de React).
Un “formulaire contrôlé” est un élément de formulaire dont la valeur ou l’état est contrôlé par le state de React. Lorsque l’utilisateur saisit des données, un gestionnaire d’événements (
-
Le Cycle de Vie des Composants (Rappel et parallèle avec les Hooks) Dans les composants de classe (qui étaient la manière standard de définir des composants avec état avant les Hooks), un composant passait par différentes phases, chacune ayant des méthodes spécifiques appelées à des moments précis. C’est ce qu’on appelle le “cycle de vie”. Comprendre ces concepts est toujours utile, même avec les Hooks, pour appréhender comment React gère les composants.
-
Montage, Mise à jour, Démontage. Les principales phases du cycle de vie sont :
-
Montage (Mounting) : Le composant est créé et inséré dans le DOM. Méthodes de classe associées :
constructor()
,static getDerivedStateFromProps()
,render()
,componentDidMount()
. C’est le moment idéal pour les configurations initiales, les appels API ou les abonnements. -
Mise à jour (Updating) : Les props ou le state du composant changent, entraînant un re-rendu. Méthodes de classe associées :
static getDerivedStateFromProps()
,shouldComponentUpdate()
,render()
,getSnapshotBeforeUpdate()
,componentDidUpdate()
. C’est le moment d’agir en réponse aux changements de props/state (par exemple, refetcher des données si un ID change). -
Démontage (Unmounting) : Le composant est retiré du DOM. Méthode de classe associée :
componentWillUnmount()
. C’est le moment pour le nettoyage : annuler les timers, les abonnements, les écouteurs d’événements. -
Comment les Hooks remplacent les méthodes de cycle de vie des composants de classe. Les Hooks fonctionnels offrent une manière plus simple et plus flexible de gérer la logique du cycle de vie en regroupant le code lié par ce qu’il fait plutôt que par où il s’exécute dans le cycle de vie.
-
componentDidMount
etcomponentDidUpdate
etcomponentWillUnmount
: Ils sont remplacés par le HookuseEffect
.componentDidMount
≈useEffect(() => { … logique … }, [])
(avec un tableau de dépendances vide)componentDidUpdate
≈useEffect(() => { … logique en réponse aux changements … }, [dependance1, dependance2])
componentWillUnmount
≈ La fonction de nettoyage retournée paruseEffect
.
-
shouldComponentUpdate
: Son rôle est géré parReact.memo
pour les composants fonctionnels etuseMemo
/useCallback
pour optimiser les valeurs ou les fonctions passées aux enfants. -
getDerivedStateFromProps
: Remplacé par la logique à l’intérieur du corps du composant fonctionnel qui calcule l’état basé sur les props, ou potentiellement paruseMemo
. -
constructor
etthis.state
: Remplacés paruseState
(ouuseReducer
pour un état plus complexe).
En résumé, alors que les méthodes de cycle de vie des classes vous obligent à diviser la logique en fonction de quand elle s’exécute,
useEffect
vous permet de placer la logique connexe (initialisation, réaction aux mises à jour, nettoyage) au même endroit, ce qui rend souvent le code plus lisible et maintenable. -
-
Le Contexte API (Approfondissement) Nous avons introduit le Hook
useContext
dans la section 10 pour la consommation de contexte. Approfondissons maintenant quand et pourquoi utiliser le Context API dans son ensemble, ainsi que ses limites.- Quand utiliser le Context API. Le Context API est la solution native de React pour le “props drilling” (passer manuellement des props à travers de nombreux niveaux de composants intermédiaires qui n’ont pas besoin de ces props eux-mêmes). Utilisez le Context API pour :
- Partager des données considérées comme “globales” ou qui affectent une grande partie de l’arbre de composants :
- Paramètres de thème (clair/sombre)
- Préférences de langue
- Informations d’authentification de l’utilisateur
- Certains états de configuration globale
- Éviter de passer des props à travers de nombreux niveaux inutiles.
Le Context API est idéal pour les données qui ne changent pas très fréquemment ou dont les changements n’ont pas un impact majeur sur les performances.
- Limitations du Context API pour la gestion de state complexe globale. Bien que le Context API soit très utile, il n’est pas un remplaçant universel pour les bibliothèques de gestion de state global (comme Redux, Zustand, etc.) dans les applications complexes pour plusieurs raisons :
- Performances : Lorsque la valeur fournie par un
Context.Provider
change, tous les composants consommateurs descendants re-rendent, même si seule une petite partie de la valeur du contexte est utilisée par un consommateur donné. Cela peut entraîner des re-rendus inutiles et des problèmes de performance si le contexte contient un objet complexe qui change souvent. Les bibliothèques de gestion de state global ont souvent des mécanismes d’optimisation plus sophistiqués pour éviter cela. - Complexité de mise à jour : Le Context API lui-même ne fournit pas de moyens structurés pour gérer la logique de mise à jour du state global. Toute la logique doit être gérée par le composant qui rend le
Provider
, potentiellement en utilisantuseState
ouuseReducer
, ce qui peut devenir complexe pour les états globaux qui ont de nombreuses actions possibles. - Débogage : Déboguer les changements de state et leur propagation dans un grand arbre via Context peut être moins évident qu’avec des outils de développement dédiés fournis par des bibliothèques comme Redux DevTools.
En bref, le Context API est excellent pour injecter des données de configuration ou d’environnement. Pour une gestion de state applicatif complexe avec des mises à jour fréquentes et des besoins de scalabilité et de performance, une bibliothèque dédiée est souvent plus appropriée (voir section 16).
-
React Router : Navigation dans l’Application Pour construire des applications monopages (Single Page Applications - SPA) navigables sans rechargement complet de la page, vous avez besoin d’une solution de routage côté client. React Router est la bibliothèque de routage la plus populaire pour React.
-
Introduction au routage côté client. Le routage côté client permet à votre application JavaScript de modifier l’URL affichée dans la barre d’adresse du navigateur et de modifier l’interface utilisateur rendue sans demander une nouvelle page au serveur. Cela donne l’impression d’une navigation rapide et fluide, comme une application native.
-
Installation et configuration de React Router. Vous pouvez installer React Router en utilisant npm, yarn ou pnpm :
npm install react-router-dom # ou yarn add react-router-dom # ou pnpm add react-router-dom
react-router-dom
est la version pour les applications web (basées sur le DOM).Pour configurer React Router, vous devez généralement envelopper votre application ou la partie que vous voulez router avec un Routeur, comme
BrowserRouter
.// Dans index.js ou App.js import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import App from './App'; import HomePage from './pages/HomePage'; import AboutPage from './pages/AboutPage'; import ContactPage from './pages/ContactPage'; const container = document.getElementById('root'); const root = ReactDOM.createRoot(container); root.render( <React.StrictMode> <BrowserRouter> {/* Enveloppe l'application */} <App /> {/* Votre composant racine où vous définissez vos routes */} </BrowserRouter> </React.StrictMode> );
- Définir des routes (
BrowserRouter
,Routes
,Route
). BrowserRouter
: C’est le routeur recommandé pour les environnements de navigation web qui utilisent l’API History d’HTML5 (pushState
,replaceState
,popstate
). Il maintient l’UI synchronisée avec l’URL.Routes
: Un conteneur pour les composantsRoute
. Il parcourt ses enfantsRoute
et rend le premier qui correspond à l’URL actuelle.Route
: Définit un chemin (path
) et le composant (element
) à rendre lorsque le chemin correspond à l’URL.
// Dans App.js import React from 'react'; import { Routes, Route } from 'react-router-dom'; import HomePage from './pages/HomePage'; import AboutPage from './pages/AboutPage'; import ContactPage from './pages/ContactPage'; import NotFoundPage from './pages/NotFoundPage'; // Composant pour page 404 import Layout from './components/Layout'; // Un composant de layout partagé (avec navigation) function App() { return ( <Routes> {/* Le conteneur de routes */} <Route path="/" element={<Layout />}> {/* Une route parente pour un layout partagé */} <Route index element={<HomePage />} /> {/* Route index pour le chemin parent "/" */} <Route path="a-propos" element={<AboutPage />} /> {/* Route pour "/a-propos" */} <Route path="contact" element={<ContactPage />} /> {/* Route pour "/contact" */} <Route path="*" element={<NotFoundPage />} /> {/* Route wildcard pour les pages non trouvées */} </Route> </Routes> ); } export default App;
- Liens de navigation (
Link
,NavLink
). Link
: Le composant standard pour créer des liens de navigation dans React Router. Il empêche le rechargement complet de la page et utilise l’historique du navigateur pour naviguer.NavLink
: Similaire àLink
, mais ajoute automatiquement des classes de style (commeactive
) à l’élément de lien lorsque la route correspond à l’URL actuelle. Utile pour mettre en évidence le lien de la page active dans un menu de navigation.
// Dans components/Layout.js (exemple de navigation) import React from 'react'; import { Link, NavLink, Outlet } from 'react-router-dom'; function Layout() { return ( <div> <nav> <ul> <li> <Link to="/">Accueil</Link> {/* Lien simple */} </li> <li> <NavLink to="/a-propos" style={({ isActive }) => ({ color: isActive ? 'green' : 'blue' })}> À Propos {/* Lien avec style actif */} </NavLink> </li> <li> <NavLink to="/contact" className={({ isActive }) => isActive ? 'link-active' : ''}> Contact {/* Lien avec classe active */} </NavLink> </li> </ul> </nav> <hr /> <Outlet /> {/* Ici seront rendus les composants des routes imbriquées */} </div> ); } export default Layout;
- Paramètres d’URL (
useParams
). Vous pouvez définir des routes avec des paramètres dynamiques (ex:/utilisateurs/:userId
). Pour accéder à ces paramètres dans le composant rendu par la route, utilisez le HookuseParams
.
// Dans pages/ProfilePage.js import React from 'react'; import { useParams } from 'react-router-dom'; // Supposons que vous avez un Hook useFetch comme défini précédemment // import useFetch from '../hooks/useFetch'; function ProfilePage() { const { userId } = useParams(); // Accède au paramètre userId de l'URL // const { data: user, loading, error } = useFetch(`https://api.example.com/users/${userId}`); // ... logique pour afficher le profil de l'utilisateur basé sur userId return ( <div> <h2>Profil de l'utilisateur</h2> <p>Affichage du profil pour l'ID : {userId}</p> {/* {loading && <p>Chargement...</p>} {error && <p>Erreur: {error.message}</p>} {user && <p>Nom: {user.name}</p>} */} </div> ); } export default ProfilePage;
Vous devrez ajouter une route dans votre configuration :
<Route path="utilisateurs/:userId" element={<ProfilePage />} />
.- Redirection (
Navigate
). Pour rediriger l’utilisateur d’une page à une autre, utilisez le composantNavigate
ou le HookuseNavigate
. Le composantNavigate
est utile pour les redirections déclaratives (par exemple, si un utilisateur non authentifié essaie d’accéder à une page protégée). Le HookuseNavigate
est utilisé pour la navigation programmatique (par exemple, après la soumission d’un formulaire réussi).
import React from 'react'; import { Navigate, useNavigate } from 'react-router-dom'; function PageProtegee({ isAuthenticated }) { // Redirection déclarative : si pas authentifié, naviguer vers /connexion if (!isAuthenticated) { return <Navigate to="/connexion" replace={true} />; {/* 'replace' évite d'ajouter la page protégée à l'historique */} } return <h2>Contenu Protégé</h2>; } function FormulaireConnexion() { const navigate = useNavigate(); // Obtient la fonction de navigation const handleSubmit = (event) => { event.preventDefault(); // Logique de connexion... const connexionReussie = true; // Exemple if (connexionReussie) { // Redirection programmatique après succès navigate('/tableau-de-bord'); // Vous pouvez aussi passer des options, comme { replace: true } } }; return ( <form onSubmit={handleSubmit}> {/* ... champs du formulaire ... */} <button type="submit">Se connecter</button> </form> ); }
Le composant
Maps
mentionné dans le sommaire original n’est pas standard pour la redirection dans React Router v6+. Le composantNavigate
ou le HookuseNavigate
sont les méthodes correctes. -
Partie 3 : Concepts Avancés et Écosystème
-
Gestion de State Complexe (Bibliothèques)
- Pourquoi utiliser une bibliothèque de gestion de state globale ?
Pour les applications React de taille moyenne à grande, le state local (
useState
,useReducer
) et le Context API (useContext
) peuvent devenir insuffisants ou difficiles à gérer à mesure que l’application grandit et que le state doit être partagé entre de nombreux composants non directement liés.
Une bibliothèque de gestion de state globale devient pertinente lorsque :
- Le state doit être partagé par de nombreux composants à différents niveaux de l’arbre.
- La logique de mise à jour du state devient complexe.
- Vous avez besoin d’une meilleure traçabilité des changements de state (debugging).
- Vous souhaitez centraliser la logique métier complexe loin des composants de présentation.
Ces bibliothèques fournissent une approche structurée pour gérer le state global de l’application, souvent avec des outils de développement puissants pour l’inspection et le débogage.
- Introduction à Redux (Concepts de base : Store, Actions, Reducers). Redux est une bibliothèque de gestion de state prévisible pour les applications JavaScript (pas seulement React). Son principe clé est d’avoir une source de vérité unique pour le state de l’application, gérée dans un Store central. Les changements de state se font uniquement en émettant des Actions (des objets décrivant ce qui s’est passé), qui sont traitées par des Reducers (des fonctions pures qui prennent l’état actuel et une action, et retournent le nouvel état).
Concepts clés de Redux :
- Store : L’objet qui contient le state global de l’application. Il n’y a qu’un seul store dans une application Redux typique.
- Actions : Des objets JavaScript simples qui décrivent ce qui s’est passé (par exemple,
{ type: 'INCREMENT', payload: 1 }
). Elles sont envoyées au store via la méthodedispatch()
. - Reducers : Des fonctions pures qui prennent le state actuel et une action, et retournent le nouvel état. Elles spécifient comment le state doit changer en réponse à une action.
(state, action) => newState
. Les reducers ne doivent pas modifier l’état directement, mais retourner un nouvel objet d’état. - Dispatch : La méthode (
store.dispatch(action)
) pour envoyer une action au store, déclenchant ainsi l’exécution des reducers et la mise à jour du state. - Selectors : Fonctions pour extraire des parties spécifiques du state du store.
Avec React, Redux est souvent utilisé avec la bibliothèque
react-redux
, qui fournit des Hooks (useSelector
,useDispatch
) pour interagir avec le store Redux depuis les composants fonctionnels.-
Introduction à Zustand, Recoil, MobX (alternatives à Redux). Redux est puissant mais peut être verbeux avec une certaine courbe d’apprentissage. Plusieurs alternatives modernes existent, souvent plus légères et plus simples à utiliser pour de nombreux cas d’usage :
-
Zustand : Une petite bibliothèque de gestion de state basée sur des Hooks, très simple et rapide à configurer. Elle utilise une approche basée sur des stores similaires à Redux mais avec une API plus simple.
-
Recoil : Une bibliothèque de gestion de state développée par Facebook, conçue spécifiquement pour React et optimisée pour la concurrence. Elle est basée sur le concept d‘“atoms” (unités de state) et de “selectors” (fonctions dérivées de l’état).
-
MobX : Une bibliothèque de gestion de state qui utilise l’observation réactive. Vous définissez un état observable, et MobX s’assure que l’UI se met à jour automatiquement lorsque l’état observé change. Elle est souvent perçue comme plus flexible et moins opinionated que Redux.
Le choix de la bibliothèque dépend de la taille et de la complexité du projet, ainsi que des préférences de l’équipe. Redux reste un choix solide pour les grandes applications nécessitant une traçabilité rigoureuse, tandis que Zustand ou Recoil peuvent être plus adaptés pour des besoins plus simples ou des équipes préférant une approche plus basée sur les Hooks.
- Pourquoi utiliser une bibliothèque de gestion de state globale ?
Pour les applications React de taille moyenne à grande, le state local (
-
Performance et Optimisation React est rapide par défaut grâce à son utilisation du Virtual DOM et de l’algorithme de réconciliation, mais dans les applications complexes, des goulots d’étranglement peuvent apparaître, souvent liés à des re-rendus excessifs.
-
Le Virtual DOM et la réconciliation.
-
Virtual DOM : Le Virtual DOM (VDOM) est une copie légère en mémoire du DOM réel. Lorsque vous écrivez du JSX, React crée un arbre d’éléments React qui représente l’interface utilisateur. Ce n’est pas le DOM réel, mais une représentation virtuelle.
-
Réconciliation (Reconciliation) : Lorsque le state ou les props d’un composant changent, React crée un nouvel arbre Virtual DOM. Ensuite, il compare (diffing) ce nouvel arbre avec l’ancien arbre Virtual DOM. Cet algorithme de comparaison est appelé la réconciliation. React identifie les différences minimales nécessaires pour mettre à jour l’UI.
-
Mise à jour du DOM réel : Enfin, React met à jour uniquement les parties du DOM réel qui ont été modifiées selon le résultat de la réconciliation. La manipulation directe du DOM est coûteuse, et en minimisant les manipulations, React optimise les performances.
-
Optimisation des re-rendus (React.memo, useMemo, useCallback). Bien que la réconciliation soit rapide, le processus de re-rendu d’un composant (l’exécution de sa fonction pour créer un nouvel arbre d’éléments React) peut devenir coûteux si l’arbre est très grand ou si les calculs sont complexes. React re-rend par défaut un composant si son parent re-rend ou si son propre state/props change. Vous pouvez optimiser cela :
-
React.memo
: Un Hook d’ordre supérieur (HOC) qui mémorise un composant fonctionnel. React évite de re-rendre le composant si ses props n’ont pas changé depuis le dernier rendu.import React from 'react'; const MonComposantMemo = React.memo(function MonComposant(props) { console.log('Rendu de MonComposant'); return <div>{props.texte}</div>; }); // Utilisation : <MonComposantMemo texte="Hello" /> // MonComposantMemo ne re-rendra que si props.texte change.
React.memo
est efficace lorsque le composant enfant est coûteux à re-rendre et que ses props ne changent pas souvent. Pour queReact.memo
fonctionne correctement, assurez-vous que les props complexes (objets, tableaux, fonctions) passées au composant memoïsé sont stables (utilisezuseMemo
pour les objets/tableaux calculés etuseCallback
pour les fonctions). -
useMemo
: Mémorise une valeur calculée (voir section 11). Évite de refaire un calcul coûteux à chaque rendu si les dépendances n’ont pas changé. -
useCallback
: Mémorise une fonction (voir section 11). Évite de recréer une fonction à chaque rendu, utile pour passer des fonctions stables à des composants enfants memoïsés ou comme dépendance dansuseEffect
. -
Lazy Loading (chargement paresseux) avec
React.lazy
etSuspense
. Le lazy loading (ou code splitting) permet de diviser le bundle JavaScript de votre application en morceaux et de charger ces morceaux à la demande, uniquement lorsque le composant est nécessaire (par exemple, lors de la navigation vers une route spécifique). Cela réduit la taille du bundle initial et améliore le temps de chargement de l’application. -
React.lazy
: Permet de charger dynamiquement un composant.// Avant : import MonComposant from './MonComposant'; // Après : const MonComposant = React.lazy(() => import('./MonComposant'));
-
Suspense
: Vous devez utiliserSuspense
au-dessus des composants lazy-loaded.Suspense
permet de définir un indicateur de chargement (fallback
) pendant que les composants dynamiques sont chargés.import React, { Suspense } from 'react'; const MonComposantLazy = React.lazy(() => import('./MonComposant')); function MaPage() { return ( <div> <h1>Ma Page</h1> <Suspense fallback={<div>Chargement...</div>}> {/* Affiche "Chargement..." pendant le chargement */}{' '} <MonComposantLazy /> {/* Le composant chargé paresseusement */} </Suspense> </div> ); }
-
Optimisation des listes (virtualisation). Le rendu de très longues listes (centaines ou milliers d’éléments) peut dégrader les performances car React crée un élément DOM pour chaque élément de la liste, même s’ils ne sont pas visibles à l’écran. La virtualisation de liste consiste à ne rendre qu’un petit sous-ensemble des éléments visibles à l’écran, et à mettre à jour ces éléments à mesure que l’utilisateur fait défiler la liste. Des bibliothèques comme
react-window
oureact-virtualized
implémentent cette technique. -
Utilisation du profiler des React Developer Tools. Le profiler est une fonctionnalité intégrée aux React Developer Tools (extension de navigateur). Il vous permet d’enregistrer l’activité de rendu de votre application et d’identifier les composants qui re-rendent fréquemment ou qui prennent beaucoup de temps à re-rendre. C’est un outil essentiel pour comprendre les performances de votre application et identifier les zones à optimiser.
-
-
Tests avec React Tester vos composants React est crucial pour garantir leur fiabilité et faciliter la maintenance. Il existe différents types de tests et des outils dédiés.
- Types de tests (unitaires, d’intégration, de bout en bout).
- Tests Unitaires : Testent de petites unités de code isolément, généralement des fonctions ou des composants individuels (par exemple, vérifier qu’un composant rend correctement une valeur donnée en props).
- Tests d’Intégration : Testent la combinaison de plusieurs unités de code pour s’assurer qu’elles fonctionnent ensemble comme prévu (par exemple, tester l’interaction entre un composant parent et un composant enfant).
- Tests de Bout en Bout (End-to-End / E2E) : Testent l’ensemble de l’application en simulant l’interaction d’un utilisateur réel dans un navigateur (par exemple, un test de bout en bout pourrait simuler un utilisateur se connectant, naviguant vers une page, remplissant un formulaire et soumettant les données).
- Introduction aux outils de test (Jest, React Testing Library, Cypress).
- Jest : Un framework de test JavaScript développé par Facebook, souvent utilisé avec React. Il inclut un runner de tests, une bibliothèque d’assertions et une bibliothèque de mocking.
- React Testing Library : Une bibliothèque qui met l’accent sur le test du comportement de l’utilisateur. Au lieu de tester les détails d’implémentation d’un composant, elle vous encourage à tester comment un utilisateur interagirait avec votre composant et ce qu’il verrait à l’écran. Elle est fortement recommandée par l’équipe React pour les tests unitaires et d’intégration.
- Cypress : Un outil de test de bout en bout qui permet d’écrire et d’exécuter des tests dans un navigateur réel. Il offre une bonne expérience de développement et un débogage visuel.
- Écrire des tests pour les composants React. Avec Jest et React Testing Library, tester un composant fonctionnel simple pourrait ressembler à ceci :
// Dans src/components/Bouton.js import React from 'react'; function Bouton({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); } export default Bouton; // Dans src/components/Bouton.test.js import { render, screen, fireEvent } from '@testing-library/react'; import Bouton from './Bouton'; import '@testing-library/jest-dom'; // Pour les matchers Jest DOM test('renders button with correct text', () => { render(<Bouton onClick={() => {}}>Cliquez-moi</Bouton>); const buttonElement = screen.getByText(/Cliquez-moi/i); // Recherche le texte insensible à la casse expect(buttonElement).toBeInTheDocument(); // Vérifie qu'il est dans le DOM }); test('handles click event', () => { const handleClick = jest.fn(); // Crée une fonction mock Jest render(<Bouton onClick={handleClick}>Cliquez-moi</Bouton>); const buttonElement = screen.getByText(/Cliquez-moi/i); fireEvent.click(buttonElement); // Déclenche un événement de clic expect(handleClick).toHaveBeenCalledTimes(1); // Vérifie que la fonction mock a été appelée 1 fois });
React Testing Library fournit des utilitaires pour rendre des composants (
render
), interagir avec eux comme un utilisateur (fireEvent
), et rechercher des éléments dans le DOM rendu (screen.getBy…
). -
Tests avec React
- Types de tests (unitaires, d’intégration, de bout en bout).
- Introduction aux outils de test (Jest, React Testing Library, Cypress).
- Écrire des tests pour les composants React.
-
Styling en React
-
Méthodes de styling (CSS classique, CSS Modules, Styled Components, Emotion, Tailwind CSS). Styliser des composants en React peut se faire de plusieurs manières, chacune avec ses avantages et inconvénients :
-
CSS classique : Utiliser des fichiers
.css
standard et les importer dans votre projet. Simples, mais peuvent rencontrer des problèmes de portée (les styles globaux peuvent affecter d’autres composants). -
CSS Modules : Crée un scope local pour les noms de classes CSS par défaut, évitant les conflits de noms. Les noms de classes sont générés dynamiquement.
// Dans MonComposant.module.css .titre { color: blue; } // Dans MonComposant.js import React from 'react'; import styles from './MonComposant.module.css'; function MonComposant() { return <h1 className={styles.titre}>Titre Stylisé</h1>; } export default MonComposant;
-
CSS-in-JS (Styled Components, Emotion) : Permet d’écrire du CSS directement dans vos fichiers JavaScript/JSX en utilisant des templates strings ou des objets JavaScript. Le CSS est scobé au composant et peut utiliser les props ou le state.
// Avec Styled Components import styled from 'styled-components'; const BoutonStyled = styled.button` background-color: ${props => props.primary ? 'blue' : 'white'}; color: ${props => props.primary ? 'white' : 'blue'}; padding: 10px; border-radius: 5px; `; function MonComposant() { return <BoutonStyled primary>Bouton Primaire</BoutonStyled>; } export default MonComposant;
-
Frameworks utilitaires (Tailwind CSS) : Une approche basée sur des classes utilitaires prédéfinies pour construire rapidement des designs en appliquant directement ces classes sur les éléments JSX. Nécessite une configuration et un processus de build.
// Avec Tailwind CSS (après configuration) function MonComposant() { return <button className="bg-blue-500 text-white p-2 rounded">Bouton Tailwind</button>; } export default MonComposant;
Le choix dépend des préférences de l’équipe, de la taille du projet et de la nécessité d’isoler les styles ou d’utiliser des fonctionnalités dynamiques basées sur le state/props.
-
Styling conditionnel et dynamique. Vous pouvez appliquer des styles conditionnellement ou dynamiquement en fonction du state ou des props :
-
Classes conditionnelles : Utilisez la logique JavaScript (ternaires,
&&
, ou des bibliothèques commeclassnames
/clsx
) pour appliquer différentes classes CSS.function Statut({ estActif }) { const classeStatut = estActif ? 'statut-actif' : 'statut-inactif'; return <span className={`statut ${classeStatut}`}>État : {estActif ? 'Actif' : 'Inactif'}</span>; }
-
Styles en ligne dynamiques : Passez un objet de style JavaScript où les valeurs peuvent être calculées dynamiquement.
function BarreProgression({ progression }) { const styleBarre = { width: `${progression}%`, backgroundColor: progression > 50 ? 'green' : 'orange', height: '20px', }; return ( <div style={{ width: '100%', backgroundColor: '#eee' }}> <div style={styleBarre}></div> </div> ); }
-
Avec CSS-in-JS : Les bibliothèques comme Styled Components permettent d’accéder directement aux props pour définir les styles.
// Exemple Styled Components vu plus haut const BoutonStyled = styled.button` background-color: ${props => props.primary ? 'blue' : 'white'}; /* ... */ `;
-
-
Fetching de Données Le fetching de données depuis une API externe est une tâche courante dans les applications React. Cela se fait généralement dans un
useEffect
pour s’assurer que les requêtes sont faites après le rendu initial et pour gérer la logique de cycle de vie (nettoyage).-
Effectuer des requêtes HTTP (Fetch API, Axios). Vous pouvez utiliser l’API native du navigateur
Fetch
ou une bibliothèque tierce commeAxios
. -
Fetch API : Intégré aux navigateurs modernes, utilise des Promesses.
useEffect(() => { fetch('https://api.example.com/data') .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { // Utiliser les données (par exemple, mettre à jour le state) console.log(data); }) .catch(error => { // Gérer les erreurs console.error('Erreur lors du fetching :', error); }); }, []); // [] signifie que l'effet s'exécute une seule fois au montage
-
Axios : Une bibliothèque populaire basée sur des Promesses qui fonctionne aussi bien côté client que côté serveur. Elle offre des fonctionnalités supplémentaires comme l’interception de requêtes/réponses, la transformation automatique des données JSON, etc.
npm install axios
import axios from 'axios'; useEffect(() => { axios.get('https://api.example.com/data') .then(response => { // response.data contient les données JSON console.log(response.data); }) .catch(error => { console.error('Erreur lors du fetching avec Axios :', error); }); }, []);
-
Gérer les états de chargement et d’erreur. Lors du fetching de données, il est important de gérer les différents états possibles : chargement, succès et erreur. Vous utilisez généralement le state local pour suivre ces états.
import React, { useState, useEffect } from 'react'; function ComposantFetch() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); // État de chargement const [error, setError] = useState(null); // État d'erreur useEffect(() => { fetch('https://api.example.com/data') .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { setData(data); setError(null); // Réinitialiser l'erreur en cas de succès }) .catch(error => { console.error('Erreur lors du fetching :', error); setError(error); // Stocker l'objet d'erreur setData(null); // Assurer que les données sont nulles en cas d'erreur }) .finally(() => { setLoading(false); // Le chargement est terminé, succès ou erreur }); }, []); if (loading) { return <p>Chargement des données...</p>; } if (error) { return <p>Erreur lors du chargement des données : {error.message}</p>; } // Afficher les données une fois chargées avec succès return ( <div> <h2>Données chargées :</h2> {/* Afficher les données ici */} {data && <pre>{JSON.stringify(data, null, 2)}</pre>} </div> ); }
L’utilisation d’une fonction de nettoyage dans
useEffect
(en retournant une fonction qui annule la requête ou nettoie les ressources) est cruciale pour éviter les fuites de mémoire ou les tentatives de mise à jour de state sur des composants démontés, surtout avec des requêtes asynchrones.- Introduction aux bibliothèques de fetching de données (React Query, SWR).
Gérer manuellement le fetching de données avec
useEffect
pour chaque requête peut devenir répétitif et complexe (gestion du cache, revalidation, synchronisation, etc.). Des bibliothèques spécialisées simplifient cette tâche : - React Query (ou TanStack Query) : Offre des Hooks puissants (
useQuery
,useMutation
) pour gérer le fetching, la mise en cache, la synchronisation et la gestion de l’état du serveur. Très populaire pour sa gestion automatique du cache et ses optimisations. - SWR : (stale-while-revalidate) Une autre bibliothèque de fetching de données avec une approche différente, souvent plus simple pour des cas basiques, mais offrant aussi des fonctionnalités avancées.
Ces bibliothèques réduisent considérablement la quantité de code boilerplate nécessaire pour le fetching de données et résolvent de nombreux problèmes courants (comme la gestion de l’état de chargement/erreur, le refetching automatique, etc.).
-
-
Gestion des Erreurs Par défaut, si une erreur JavaScript non rattrapée se produit dans le rendu, une méthode de cycle de vie ou un gestionnaire d’événement (dans certaines conditions) d’un composant React, cela fait planter toute l’application en affichant un écran blanc. Les Error Boundaries sont un concept React pour gérer gracieusement ces erreurs et afficher une UI de fallback au lieu de crasher.
- Les Error Boundaries pour capturer les erreurs dans l’arbre des composants.
Un Error Boundary est un composant de classe (c’est l’un des rares cas où un composant de classe est encore nécessaire dans le développement moderne) qui implémente soit la méthode de cycle de vie
static getDerivedStateFromError()
soitcomponentDidCatch()
. static getDerivedStateFromError(error)
: Permet de mettre à jour le state pour afficher une UI de fallback après qu’une erreur a été lancée.componentDidCatch(error, errorInfo)
: Permet de réaliser des effets de bord après une erreur, comme enregistrer l’information de l’erreur dans un service de log.
Un Error Boundary capture les erreurs dans son arbre de composants enfants, mais pas dans son propre code. Vous enveloppez la partie de votre UI que vous voulez protéger avec l’Error Boundary.
import React from 'react'; class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null, errorInfo: null }; } // Cette méthode est appelée si une erreur est lancée dans les enfants. // Elle met à jour le state pour le prochain rendu. static getDerivedStateFromError(error) { // Mettre à jour l'état pour que le prochain rendu affiche l'UI de fallback. return { hasError: true }; } // Cette méthode est également appelée si une erreur est lancée dans les enfants. // Elle est utile pour les effets de bord comme le logging d'erreurs. componentDidCatch(error, errorInfo) { // Vous pouvez également loguer l'erreur dans un service de reporting d'erreurs console.error("Erreur non rattrapée par ErrorBoundary:", error, errorInfo); this.setState({ error: error, errorInfo: errorInfo }); } render() { if (this.state.hasError) { // Vous pouvez afficher n'importe quelle UI de fallback return ( <div style={{ border: '1px solid red', padding: '10px', color: 'red' }}> <h2>Quelque chose s\'est mal passé.</h2> <details style={{ whiteSpace: 'pre-wrap' }}> {this.state.error && this.state.error.toString()} <br /> {this.state.errorInfo && this.state.errorInfo.componentStack} </details> </div> ); } return this.props.children; // Rendre les enfants normalement s'il n'y a pas d'erreur } } // Utilisation de l'Error Boundary function ComposantQuiPourraitCrasher() { // Simuler une erreur pour le test // throw new Error('Ceci est une erreur de test!'); return <p>Ce composant fonctionne bien (pour l'instant).</p>; } function App() { return ( <div> <h1>Application</h1> <ErrorBoundary> {/* Enveloppe la partie à protéger */} <ComposantQuiPourraitCrasher /> </ErrorBoundary> <p>Ce contenu n'est pas affecté par les erreurs ci-dessus.</p> </div> ); }
Il est recommandé de placer des Error Boundaries à des endroits stratégiques dans votre application (par exemple, autour de routes principales, de widgets, ou de sections potentiellement instables) plutôt qu’autour de chaque composant individuel.
- Les Error Boundaries pour capturer les erreurs dans l’arbre des composants.
Un Error Boundary est un composant de classe (c’est l’un des rares cas où un composant de classe est encore nécessaire dans le développement moderne) qui implémente soit la méthode de cycle de vie
-
Fetching de Données
- Effectuer des requêtes HTTP (Fetch API, Axios).
- Gérer les états de chargement et d’erreur.
- Introduction aux bibliothèques de fetching de données (React Query, SWR).
-
Gestion des Erreurs
- Les Error Boundaries pour capturer les erreurs dans l’arbre des composants.
-
Server-Side Rendering (SSR) et Static Site Generation (SSG) Bien que React soit principalement utilisé pour le rendu côté client (Client-Side Rendering - CSR), où le navigateur télécharge un bundle JavaScript et construit le DOM, le SSR et le SSG sont des techniques alternatives qui améliorent la performance et le SEO. Ces techniques sont généralement implémentées en utilisant des frameworks construits sur React.
- Introduction aux concepts.
- Client-Side Rendering (CSR) : Le HTML initial envoyé par le serveur est minimal (souvent juste une div vide comme
<div id="root"></div>
). Le navigateur télécharge le JavaScript, qui ensuite exécute React pour construire l’UI et l’injecter dans le DOM.- Avantages : Interaction rapide une fois l’application chargée, bon pour les applications web dynamiques.
- Inconvénients : Temps de chargement initial plus long (bundle JS à télécharger et exécuter), moins bon pour le SEO (les moteurs de recherche peuvent avoir du mal à indexer le contenu généré par JS).
- Server-Side Rendering (SSR) : L’application React est d’abord rendue sur le serveur en HTML. Ce HTML est envoyé au navigateur, ce qui permet un affichage rapide du contenu. Ensuite, le JavaScript est téléchargé et exécuté dans le navigateur pour rendre l’application interactive (ce processus s’appelle l’hydratation).
- Avantages : Meilleur SEO (le contenu HTML est disponible immédiatement pour les crawlers), performance initiale perçue plus rapide (l’utilisateur voit quelque chose rapidement).
- Inconvénients : Temps de réponse du serveur potentiellement plus long, complexité accrue.
- Static Site Generation (SSG) : L’application React est rendue en HTML au moment de la compilation/build. Les fichiers HTML statiques générés sont ensuite servis par un serveur web ou un CDN. Le JavaScript est ensuite chargé dans le navigateur pour hydrater l’application et la rendre interactive.
- Avantages : Très rapide (les fichiers statiques sont servis directement), excellent SEO, moins cher à héberger (pas besoin de serveur dynamique pour chaque requête).
- Inconvénients : Moins adapté aux applications avec beaucoup de contenu dynamique qui change fréquemment (chaque changement nécessite un nouveau build), prend plus de temps lors du build pour les sites très importants.
- Présentation des frameworks (Next.js, Remix) qui implémentent le SSR/SSG. Gérer le SSR ou le SSG manuellement avec React peut être complexe. C’est pourquoi des frameworks dédiés existent :
- Next.js : Le framework React le plus populaire pour le rendu côté serveur et la génération de sites statiques. Il offre une structure de projet basée sur les pages, une API Routes intégrée, le code splitting automatique, et prend en charge le SSR (
getServerSideProps
), le SSG (getStaticProps
), et l’Incremental Static Regeneration (ISR). Développé par Vercel. - Remix : Un autre framework React moderne qui se concentre sur les principes web fondamentaux (formulaires, requêtes HTTP) et offre une excellente expérience développeur pour le SSR et le SSG.
- Gatsby : Principalement orienté SSG, très populaire pour les sites statiques basés sur des données (comme des blogs ou des sites documentaires) provenant de diverses sources (Markdown, CMS, APIs).
- Avantages (SEO, performance initiale). Comme mentionné ci-dessus, les principaux avantages du SSR et du SSG par rapport au CSR pur sont :
- SEO (Search Engine Optimization) : Le contenu de la page est présent dans le HTML initial, ce qui facilite son indexation par les moteurs de recherche.
- Performance initiale perçue : L’utilisateur voit le contenu de la page plus rapidement car il est servi directement en HTML, avant même que le JavaScript ne soit entièrement chargé et exécuté (Time To First Contentful Paint - FCP).
- Performance réelle (pour SSG) : Les sites statiques sont extrêmement rapides à servir car il n’y a pas de calcul côté serveur pour chaque requête.
-
TypeScript avec React TypeScript est un sur-ensemble de JavaScript qui ajoute le typage statique. L’utilisation de TypeScript dans un projet React permet de détecter de nombreuses erreurs courantes pendant le développement plutôt qu’à l’exécution, ce qui améliore la fiabilité, la maintenabilité et l’expérience développeur (autocomplétion, refactoring).
-
Intégration de TypeScript dans un projet React.
-
Nouveau projet : La plupart des outils modernes pour créer des applications React (comme Create React App, Vite, Next.js) offrent un template TypeScript lors de la création du projet.
# Avec Create React App npx create-react-app mon-app-ts --template typescript # Avec Vite npm create vite@latest mon-app-ts --template react-ts
-
Projet existant : Vous pouvez ajouter TypeScript à un projet JavaScript existant en installant les dépendances nécessaires (
typescript
,@types/react
,@types/react-dom
, et potentiellement des types pour d’autres bibliothèques que vous utilisez) et en configurant un fichiertsconfig.json
.
Les fichiers JavaScript (
.js
,.jsx
) sont généralement renommés en.ts
ou.tsx
(pour les fichiers contenant du JSX).- Types pour les props, le state, les événements. L’un des principaux avantages de TypeScript avec React est de typer explicitement les props et le state de vos composants.
Typage des Props (Composants Fonctionnels) : Vous définissez une interface ou un type pour les props et vous l’appliquez au paramètre de la fonction du composant.
import React from 'react'; // Définir le type des props interface GreetingProps { name: string; age?: number; // ? indique que la prop est facultative } function Greeting(props: GreetingProps) { return ( <h1> Bonjour, {props.name}! {props.age !== undefined && <p>Vous avez {props.age} ans.</p>} </h1> ); } export default Greeting; // Utilisation : // <Greeting name="Alice" /> // Valide // <Greeting name="Bob" age={30} /> // Valide // <Greeting age={25} /> // Erreur TypeScript : 'name' est manquant et requis // <Greeting name="Charlie" age="trente" /> // Erreur TypeScript : 'age' doit être un nombre
Typage du State (avec
useState
) : TypeScript peut souvent inférer le type du state à partir de la valeur initiale. Cependant, il est recommandé de spécifier explicitement le type pour plus de clarté, surtout si l’état peut êtrenull
ouundefined
initialement.import React, { useState } from 'react'; interface User { id: number; name: string; } function UserProfile() { // Spécifie que l'état 'user' peut être User ou null const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState<boolean>(true); const [count, setCount] = useState<number>(0); // ... logique de fetching ou mise à jour du state }
Typage des Événements : Les gestionnaires d’événements reçoivent un objet événement dont le type dépend du type de l’élément DOM et de l’événement. TypeScript fournit des types prédéfinis pour ces événements (par exemple,
React.ChangeEvent
,React.MouseEvent
,React.FormEvent
).import React, { useState, ChangeEvent, MouseEvent, FormEvent } from 'react'; function MonFormulaire() { const [inputValue, setInputValue] = useState<string>(''); const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => { setInputValue(event.target.value); }; const handleButtonClick = (event: MouseEvent<HTMLButtonElement>) => { console.log('Bouton cliqué', event.clientX); }; const handleSubmit = (event: FormEvent<HTMLFormElement>) => { event.preventDefault(); console.log('Formulaire soumis avec valeur:', inputValue); }; return ( <form onSubmit={handleSubmit}> <input type="text" value={inputValue} onChange={handleInputChange} /> <button type="button" onClick={handleButtonClick}>Cliquez</button> <button type="submit">Soumettre</button> </form> ); }
L’utilisation de TypeScript ajoute une couche de sécurité et améliore la robustesse de votre code React.
-
-
Bonnes Pratiques et Modèles de Conception Adopter de bonnes pratiques et comprendre les modèles de conception courants peut grandement améliorer la lisibilité, la maintenabilité et la scalabilité de vos applications React.
-
Convention de nommage.
-
Composants : Toujours utiliser la casse PascalCase (ou UpperCamelCase) pour les noms de composants (ex:
UserProfile
,ProductList
). Cela permet à React de distinguer les composants des éléments HTML natifs. -
Hooks personnalisés : Commencer le nom par
use
(ex:useFetch
,useLocalStorage
). -
Fichiers : Nommer les fichiers de composants en utilisant la casse PascalCase (
UserProfile.js
,ProductList.jsx
) ou kebab-case (user-profile.js
,product-list.jsx
). La casse PascalCase est courante, surtout si le nom de fichier correspond au nom du composant principal qu’il exporte. -
Variables/Fonctions : Utiliser la casse camelCase pour les variables, les fonctions et les noms de props (ex:
userName
,handleClick
,fetchData
). -
CSS/Styles : Utiliser kebab-case pour les classes CSS (
.ma-classe
) ou camelCase si vous utilisez des objets de style JavaScript ou certains outils CSS-in-JS. -
Organisation du code. Il n’y a pas une seule bonne façon d’organiser un projet React, mais la cohérence est essentielle. Voici quelques approches courantes :
-
Par fonctionnalité/module : Regrouper les fichiers (composants, Hooks, styles, tests, etc.) qui appartiennent à une même fonctionnalité dans un dossier unique.
src/ ├── features/ │ ├── auth/ │ │ ├── components/ │ │ │ ├── LoginForm.js │ │ │ └── RegisterForm.js │ │ ├── hooks/ │ │ │ └── useAuth.js │ │ ├── api/ │ │ │ └── authApi.js │ │ └── AuthView.js │ └── products/ │ ├── components/ │ ├── hooks/ │ └── ProductsPage.js ├── components/ # Pour les composants génériques/partagés (boutons, modales...) ├── hooks/ # Pour les hooks génériques ├── utils/ # Fonctions utilitaires ├── App.js └── index.js
-
Par type de fichier : Regrouper tous les composants dans un dossier
components
, tous les Hooks danshooks
, etc.src/ ├── components/ │ ├── Auth/ │ │ ├── LoginForm.js │ │ └── RegisterForm.js │ ├── Products/ │ │ └── ProductList.js │ └── Shared/ # Composants génériques │ └── Button.js ├── hooks/ │ ├── useAuth.js │ └── useFetch.js ├── pages/ # Pour les composants de page │ ├── HomePage.js │ └── ProductsPage.js ├── api/ ├── utils/ ├── App.js └── index.js
L’approche par fonctionnalité est souvent préférée pour les applications de taille moyenne à grande car elle rend plus facile de trouver tous les fichiers liés à une tâche spécifique.
- Composants conteneurs vs composants de présentation. Un modèle populaire (bien que parfois contesté avec les Hooks) est de séparer les composants en deux catégories :
- Composants de présentation (ou “Dumb” / “Pure”) : Se préoccupent de comment les choses s’affichent. Ils reçoivent les données et les callbacks via des props et n’ont généralement pas de state interne (ou un state très simple lié à l’UI, comme l’état ouvert/fermé d’une modale). Ils sont souvent plus faciles à réutiliser et à tester.
- Composants conteneurs (ou “Smart” / “Stateful”) : Se préoccupent de ce que les choses font. Ils contiennent la logique métier, le fetching de données, la gestion du state complexe, et appellent les actions (si vous utilisez Redux). Ils ne rendent généralement pas de balisage HTML eux-mêmes, mais rendent d’autres composants (souvent des composants de présentation) en leur passant les données et les callbacks nécessaires via des props.
Avec les Hooks, cette distinction est moins stricte car un composant fonctionnel peut facilement gérer le state et la logique. Cependant, le principe de séparer la logique de récupération/gestion des données de la logique de présentation reste pertinent et peut être atteint en utilisant des Hooks personnalisés (qui gèrent la logique et l’état) qui sont ensuite consommés par des composants (qui se concentrent sur le rendu).
- Principes SOLID dans le développement React. Bien que les principes SOLID viennent de la conception orientée objet, les idées sous-jacentes peuvent s’appliquer au développement React :
- Single Responsibility Principle (SRP) : Un composant ou un Hook devrait avoir une seule raison de changer. Un composant ne devrait pas être responsable à la fois du fetching de données, de la gestion d’un formulaire complexe et de l’affichage d’une liste. Séparez ces responsabilités en différents Hooks ou composants.
- Open/Closed Principle (OCP) : Les composants/modules devraient être ouverts à l’extension mais fermés à la modification. Concevez des composants de manière à ce que leur comportement puisse être étendu (par composition, props, children) sans avoir à modifier leur code source interne.
- Liskov Substitution Principle (LSP) : Les objets d’une superclasse devraient pouvoir être remplacés par des objets de ses sous-classes sans affecter la justesse du programme. Moins directement applicable aux composants fonctionnels purs, mais pertinent si vous utilisez l’héritage (rare en React moderne) ou si vous concevez des interfaces de props pour que des composants puissent être interchangeables.
- Interface Segregation Principle (ISP) : Les clients ne devraient pas être forcés de dépendre d’interfaces qu’ils n’utilisent pas. Pour React, cela signifie concevoir des props concises et spécifiques plutôt que de passer un gros objet de configuration unique à un composant qui n’a besoin que de quelques propriétés.
- Dependency Inversion Principle (DIP) : Les modules de haut niveau ne devraient pas dépendre de modules de bas niveau. Les deux devraient dépendre d’abstractions. Les abstractions ne devraient pas dépendre des détails. Les détails devraient dépendre des abstractions. En React, cela peut se traduire par l’injection de dépendances (par props ou Context) ou l’utilisation de Hooks personnalisés pour abstraire la logique de bas niveau (comme l’accès à une API ou à un service).
-
-
Déploiement d’une Application React Une fois que vous avez développé votre application React, vous devez la préparer pour la production et la rendre accessible aux utilisateurs.
- Construire l’application pour la production. Le code source de votre application React utilise généralement des fonctionnalités modernes de JavaScript/JSX qui ne sont pas directement comprises par tous les navigateurs. Le processus de “build” (construction) compile, transpile, optimise et regroupe votre code en fichiers statiques (HTML, CSS, JavaScript) qui peuvent être servis par un serveur web standard.
Les outils comme Create React App, Vite, Next.js ont des commandes intégrées pour cela :
-
Create React App :
npm run build # ou yarn build
Cela crée un dossier
build/
(oudist/
) contenant les fichiers optimisés prêts à être déployés. -
Vite :
npm run build # ou yarn build
Cela crée généralement un dossier
dist/
avec les fichiers de production. -
Next.js :
npm run build
Next.js génère un dossier
.next/
et peut exporter des sites statiques ou préparer une application pour le SSR/ISR.
Ce processus inclut généralement :
-
Transpilation (Babel) : Conversion du code JavaScript/JSX moderne en code compatible avec plus de navigateurs.
-
Bundling (Webpack, Rollup, esbuild) : Regroupement de tous les fichiers JavaScript, CSS, et assets en un petit nombre de fichiers optimisés.
-
Minification : Suppression des espaces blancs, commentaires et renommage des variables pour réduire la taille des fichiers.
-
Optimisation des assets : Compression des images, utilisation de formats efficaces.
-
Code Splitting : Division du code en morceaux pour le lazy loading.
-
Déployer sur des plateformes (Netlify, Vercel, Heroku, etc.). Les fichiers statiques générés par le processus de build peuvent être déployés sur une variété de plateformes d’hébergement. Le choix dépend si vous déployez un site statique pur (SSG), une application côté client (CSR), ou une application nécessitant un serveur (SSR, API routes).
-
Hébergement de sites statiques (pour CSR ou SSG) : Ces plateformes sont idéales pour servir les fichiers statiques générés par le build. Elles offrent souvent des CDN (Content Delivery Network) pour une distribution rapide et mondiale.
- Netlify: Très populaire, offre le déploiement depuis Git, la configuration facile de domaines, les fonctions serverless, etc.
- Vercel: Similaire à Netlify, optimisé pour les applications Next.js (car développé par la même entreprise), offre des fonctions serverless, etc.
- GitHub Pages: Simple et gratuit pour héberger des sites statiques directement depuis un dépôt GitHub.
- GitLab Pages: Similaire à GitHub Pages.
- Firebase Hosting: Service d’hébergement rapide et sécurisé de Google.
- AWS S3 + CloudFront: Solution d’hébergement de fichiers statiques avec CDN via Amazon Web Services.
-
Hébergement d’applications dynamiques (pour SSR ou applications avec API) : Ces plateformes peuvent exécuter un serveur Node.js ou des fonctions serverless pour les applications SSR ou celles ayant une logique côté serveur.
- Vercel/Netlify: Supportent également les applications SSR/Serverless (notamment avec Next.js/Remix).
- Heroku: Plateforme PaaS (Platform as a Service) simple pour déployer des applications web nécessitant un serveur backend.
- Render: Une alternative moderne à Heroku.
- AWS Elastic Beanstalk / EC2: Options plus flexibles (et complexes) sur Amazon Web Services.
- Google Cloud Platform (App Engine, Cloud Run, Compute Engine): Options d’hébergement sur Google Cloud.
- Microsoft Azure (App Service): Options d’hébergement sur Azure.
Le processus de déploiement implique généralement de lier votre dépôt Git à la plateforme d’hébergement, qui peut alors automatiquement reconstruire et déployer votre application à chaque push.
-
L’Écosystème React et les Outils React n’est qu’une bibliothèque pour la couche de vue. Autour de React s’est développé un vaste écosystème d’outils et de bibliothèques pour couvrir d’autres aspects du développement web (build, routage, gestion de state, testing, styling, etc.).
- Create React App vs Vite. Ce sont deux outils populaires pour initialiser rapidement un projet React avec un environnement de développement et de build configuré :
- Create React App (CRA) : L’outil officiel historique pour démarrer un projet React. Il inclut une configuration complète (Webpack, Babel, ESLint, etc.) “sous le capot”, ce qui le rend facile à utiliser sans configuration, mais peut être lent pour les grands projets et moins flexible si vous avez besoin de personnaliser la configuration de build (
eject
). - Vite : Un outil de build plus moderne qui tire parti des modules ES natifs du navigateur pendant le développement. Cela rend le démarrage et les mises à jour à chaud (Hot Module Replacement - HMR) beaucoup plus rapides, en particulier pour les grands projets. Il est également plus léger et plus flexible à configurer. Vite est de plus en plus préféré pour les nouveaux projets React.
- Storybook pour le développement de composants isolés. Storybook est un environnement de développement interactif pour les composants d’interface utilisateur. Il permet de développer, tester et documenter les composants UI en isolation, en dehors de l’application principale. Pour chaque composant, vous écrivez des “stories” qui représentent différents états visuels.
- Avantages : Développement isolé (plus rapide et focalisé), documentation visuelle des composants, tests de régression visuelle, collaboration entre développeurs et designers.
- Installation : Vous pouvez généralement l’ajouter à un projet existant avec une commande CLI (
npx storybook init
). - Linters (ESLint) et formateurs de code (Prettier). Ces outils sont essentiels pour maintenir la qualité, la cohérence et la lisibilité du code dans un projet, surtout en équipe.
- Linters (ESLint) : Analysent le code statiquement pour identifier les problèmes potentiels, les erreurs de syntaxe, les violations de style et les mauvaises pratiques. ESLint est le linter standard pour JavaScript/React. Il est hautement configurable et permet d’utiliser des ensembles de règles recommandées (comme celles de Create React App, Airbnb, Standard JS). Souvent utilisé avec des plugins spécifiques à React (
eslint-plugin-react
,eslint-plugin-react-hooks
). - Formateurs de code (Prettier) : Appliquent automatiquement des règles de formatage du code (indentation, espaces, ponctuation, longueur des lignes, etc.) pour assurer une cohérence stylistique à travers tout le projet. Prettier s’intègre bien avec ESLint et les éditeurs de code.
L’intégration de ces outils dans votre flux de développement (avec des extensions d’éditeur, des hooks de pré-commit) permet d’attraper les problèmes tôt et de maintenir un codebase propre et cohérent.
Conclusion Ce guide a couvert les fondamentaux de React, les Hooks, la gestion de l’état, le routage, la performance, les tests, le styling, le fetching de données, la gestion des erreurs, les options de rendu (SSR/SSG) et les outils clés de l’écosystème.
React est une bibliothèque flexible et puissante pour construire des interfaces utilisateur modernes. En maîtrisant les concepts abordés, vous êtes bien équipé pour créer des applications web performantes et maintenables.
L’apprentissage de React est un voyage continu. L’écosystème évolue, de nouvelles bibliothèques et techniques apparaissent. Restez curieux, pratiquez régulièrement et n’hésitez pas à plonger dans la documentation officielle de React et les ressources communautaires.
Bon codage avec React !
-
TypeScript avec React
- Intégration de TypeScript dans un projet React.
- Types pour les props, le state, les événements.
-
Bonnes Pratiques et Modèles de Conception
- Convention de nommage.
- Organisation du code.
- Composants conteneurs vs composants de présentation.
- Principes SOLID dans le développement React.
-
Déploiement d’une Application React
- Construire l’application pour la production.
- Déployer sur des plateformes (Netlify, Vercel, Heroku, etc.).
-
L’Écosystème React et les Outils
- Create React App vs Vite.
- Storybook pour le développement de composants isolés.
- Linters (ESLint) et formateurs de code (Prettier).
Conclusion