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 et npm -v (ou yarn -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.
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 reactcd mon-app-reactnpm installnpm 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 fichier public/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.
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 de class, htmlFor au lieu de for).
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 HTMLconst element = <a href="https://reactjs.org"> Lien vers React </a>;// Attributs en camelCaseconst elementAvecClasse = <div className="ma-classe"> Contenu </div>;// Attributs avec expressions JSconst imageUrl = 'chemin/vers/image.jpg';const imageElement = <img src={imageUrl} alt="Une image" />;
Différences avec le HTML classique.
Utilisation de className au lieu de class pour spécifier des classes CSS.
Utilisation de htmlFor au lieu de for 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.
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éthode render() 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 simpleimport 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 et import pour l’utiliser dans un autre fichier.
// Dans Salutation.jsimport React from 'react';function Salutation(props) { return <h1>Bonjour, {props.nom} !</h1>;}export default Salutation;// Dans App.jsimport React from 'react';import Salutation from './Salutation'; // Chemin relatiffunction 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.jsimport React from 'react';function Bouton(props) { return <button onClick={props.onClick}>{props.texte}</button>;}export default Bouton;// Dans Toolbar.jsimport 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 composant Bouton.
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 Parentfunction Parent() { const message = "Salut de la part du parent !"; return <Enfant texte={message} />;}// Composant Enfantfunction 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 composant Enfant via la prop nommée texte. Le composant Enfant reçoit ces données dans un objet props (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.
C’est l’équivalent de const { texte } = props; à l’intérieur de la fonction.
props.children.
La prop spéciale props.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 Cardfunction 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 Cardfunction App() { return ( <Card> <h2>Titre de la carte</h2> <p>Contenu de la carte.</p> </Card> );}
Ici, props.children dans le composant Card 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 Hook useState 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 state count à 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 state count.
setCount(count + 1) : Appeler cette fonction avec une nouvelle valeur déclenche un re-rendu du composant avec la nouvelle valeur de count.
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 par useState.
Le state est asynchrone (comprendre la mise à jour).
Les mises à jour de state via setCount (ou this.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 à jourfunction 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éesfunction 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 composant Calculator (l’ancêtre commun). Il passe celsius comme prop à TemperatureInput et FahrenheitDisplay, et il passe également la fonction handleCelsiusChange comme prop onTemperatureChange à 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 est true, l’élément après && est rendu. Si la condition est false, React ignore l’élément.
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.
La fonction map() itère sur le tableau fruits 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 prop key unique pour chaque élément de la liste. React utilise les key 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.
Partie 2 : Hooks et Concepts Intermédiaires
Les Hooks de Base (Approfondissement)
useEffect : Gérer les effets de bord (appels API, interactions DOM, timers).
Le Hook useEffect 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 de useEffect 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.jsimport React from 'react';const ThemeContext = React.createContext('light'); // 'light' est la valeur par défautexport default ThemeContext;
Fournir une valeur de contexte : Utilisez le .Provider du contexte créé plus haut dans l’arbre des composants.
// Dans App.jsimport 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.
useRef : Accéder aux éléments du DOM ou conserver des valeurs mutables sans provoquer de re-rendu.
Le Hook useRef 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 Hook useReducer 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 fonction dispatch pour déclencher des actions.
import React, { useReducer } from 'react';// Le Reducer : prend l'état actuel et une action, retourne le nouvel étatfunction 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 useReducerfunction 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 Hook useCallback 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.memofunction 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 fonction handleClick serait recréée à chaque rendu de ParentAvecCallback, provoquant un re-rendu de Bouton même si ses “données” n’ont pas changé.
useMemo : Mémoriser des valeurs calculées pour optimiser les performances.
Le Hook useMemo 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 fonction filter s’exécuterait à chaque rendu de ListeComplexe, même si seul le count 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 composantimport 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 composant UserProfile 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éclenche handleChange, 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’objet event.target :
<input type="checkbox"> : Utilisez event.target.checked (un booléen).
<input type="radio"> : Similaire aux checkboxes, utilisez event.target.checked et event.target.value pour identifier quel bouton est sélectionné.
<select> : Utilisez event.target.value pour la valeur sélectionnée (ou event.target.value avec l’attribut multiple pour les sélections multiples, bien que cela soit souvent géré différemment dans des cas plus complexes).
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 gestionnaire onChange 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.
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 et componentDidUpdate et componentWillUnmount : Ils sont remplacés par le Hook useEffect.
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 par useEffect.
shouldComponentUpdate : Son rôle est géré par React.memo pour les composants fonctionnels et useMemo/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 par useMemo.
constructor et this.state : Remplacés par useState (ou useReducer 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 utilisant useState ou useReducer, 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 :
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.jsimport 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 composants Route. Il parcourt ses enfants Route 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.jsimport 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 404import 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 (comme active) à 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 Hook useParams.
// Dans pages/ProfilePage.jsimport 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 composant Navigate ou le Hook useNavigate. Le composant Navigate est utile pour les redirections déclaratives (par exemple, si un utilisateur non authentifié essaie d’accéder à une page protégée). Le Hook useNavigate 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 composant Navigate ou le Hook useNavigate 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éthode dispatch().
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.
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 que React.memo fonctionne correctement, assurez-vous que les props complexes (objets, tableaux, fonctions) passées au composant memoïsé sont stables (utilisez useMemo pour les objets/tableaux calculés et useCallback 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 dans useEffect.
Lazy Loading (chargement paresseux) avec React.lazy et Suspense.
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 utiliser Suspense 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 ou react-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.jsimport React from 'react';function Bouton({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> );}export default Bouton;// Dans src/components/Bouton.test.jsimport { render, screen, fireEvent } from '@testing-library/react';import Bouton from './Bouton';import '@testing-library/jest-dom'; // Pour les matchers Jest DOMtest('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.jsimport 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.
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.
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 comme classnames/clsx) pour appliquer différentes classes CSS.
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 hautconst 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 comme Axios.
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() soit componentDidCatch().
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 Boundaryfunction 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.
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 Appnpx create-react-app mon-app-ts --template typescript# Avec Vitenpm 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 fichier tsconfig.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 propsinterface 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 être null ou undefined 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).
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.
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# ouyarn build
Cela crée un dossier build/ (ou dist/) contenant les fichiers optimisés prêts à être déployés.
Vite :
npm run build# ouyarn 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).