Skip to content

5. Chapitre 4 - Introduction à [React]

5.1. Sources

Ce chapitre prend appui sur la documentation officielle de [React] : react.dev.

5.2. D’AngularJS 1.x à [React]

Le document original (2014) utilisait AngularJS 1.x : un framework JavaScript (pas encore TypeScript) organisé autour de $scope, de contrôleurs, de directives (ng-repeat, ng-if…) et d’un mécanisme de détection des changements fondé sur la « boucle de digestion » ($digest) et la surveillance de variables ($watch).

[React] (créé par Meta, 2013) n’est pas un framework complet comme AngularJS 1.x ou [Angular] : c’est une bibliothèque dédiée à un seul problème, construire des interfaces à partir de composants. Elle ne fournit, par elle-même, ni client HTTP, ni routeur, ni mécanisme d’injection de dépendances - c’est pourquoi le client [RdvMedecins] de ce chapitre s’appuie, pour ces besoins, sur l’API native du navigateur (fetch(), cf. plus loin) et sur un mécanisme propre à [React], le Context, plutôt que sur des modules dédiés comme [HttpClient] ou le routeur d’[Angular]. Ce chapitre présente ces notions une par une, avec de petits exemples indépendants de l’étude de cas - le chapitre suivant les retrouvera à l’œuvre dans le client [RdvMedecins] complet, en [React] 19.

5.3. Le principe d’une application [React] : le « Single Page Application »

Une application [React] est, comme une application [Angular], une application monopage (« Single Page Application », SPA) : le serveur ne délivre au navigateur qu’une seule page HTML, contenant un unique fichier JavaScript qui embarque toute la logique de l’application. Une fois cette page chargée, il n’y a plus jamais de rechargement de page complet : chaque interaction de l’utilisateur (changer de vue, envoyer un formulaire…) est traitée par le code JavaScript déjà présent dans le navigateur, qui met à jour l’affichage et dialogue avec le serveur en arrière-plan via des requêtes HTTP asynchrones (le plus souvent en JSON). C’était déjà le principe du document original avec AngularJS 1.x, et de la variante [Angular] de ce cours - il n’a pas changé : seule la bibliothèque qui gère l’affichage diffère d’une variante à l’autre.

5.4. Les composants : fonctions et [JSX]

Comme en [Angular], un composant est l’unité de base d’une application [React] : il produit ce qui doit s’afficher à l’écran, à partir de l’état qu’il détient et des propriétés qu’il reçoit. La différence tient à la forme : un composant [Angular] est une classe décorée, associée à un template HTML séparé (ou inline) ; un composant [React] est simplement une fonction JavaScript (ou TypeScript) qui retourne du [JSX] - une syntaxe qui ressemble à du HTML, mais qui est en réalité transformée par le compilateur en appels de fonctions JavaScript ordinaires.

Voici un tout petit composant, indépendant de l’étude de cas, équivalent au [CompteurComponent] présenté dans la variante [Angular] de ce cours :

// Compteur.tsx

import { useState } from 'react';

export function Compteur() {
  const [valeur, setValeur] = useState(0);

  function incrementer(): void {
    setValeur((v) => v + 1);
  }

  return (
    <>
      <p>Valeur : {valeur}</p>
      <button onClick={incrementer}>+1</button>
    </>
  );
}

Commentons ce code :

  • ligne 3 : import { useState } from 'react';[useState] est un hook [React] : une fonction spéciale (reconnaissable à son préfixe use) qui permet à une fonction composant de détenir un état qui survit d’un rendu à l’autre - un rôle comparable à celui d’un signal côté [Angular], ou du couple $scope/contrôleur d’AngularJS 1.x ;
  • ligne 5 : export function Compteur() { — un composant [React] est une fonction ordinaire, exportée : il n’y a ni décorateur, ni sélecteur HTML personnalisé à déclarer (contrairement à [@Component]({ selector: … }) côté [Angular]) - on l’utilise directement comme une balise portant son propre nom, <Compteur />, dans le [JSX] d’un composant parent ;
  • ligne 6 : const [valeur, setValeur] = useState(0);[useState](0) crée une valeur d’état initialisée à 0, et renvoie une paire : la valeur courante (valeur) et une fonction pour la remplacer (setValeur). La déstructuration de tableau ([valeur, setValeur]) est une simple convention de nommage - rien n’empêche d’appeler ces deux éléments autrement ;
  • lignes 8-10 : setValeur((v) => v + 1); — appeler setValeur ne modifie pas valeur sur place (les valeurs d’état [React] sont immuables) : elle demande à [React] de redessiner le composant avec la nouvelle valeur calculée à partir de l’ancienne, exactement comme monSignal.update(v => …) côté [Angular] ;
  • lignes 13-16 : <p>Valeur : {valeur}</p> — le [JSX] retourné par la fonction EST le template : les accolades { } y insèrent une expression JavaScript (ici, la valeur courante) - jouant le même rôle que les doubles accolades {{ valeur() }} du template [Angular], mais sans les parenthèses d’appel : valeur est directement la donnée, pas un signal qu’il faudrait lire en l’appelant.

La balise <> … </> (un « fragment ») regroupe plusieurs éléments sans ajouter de nœud HTML superflu autour d’eux - un détail de plomberie du [JSX], qui exige qu’une fonction composant ne retourne qu’un seul élément racine.

5.5. L’état local : [useState] et les props

En AngularJS 1.x, [Angular] détectait qu’une donnée avait changé grâce à une boucle de vérification systématique (le $digest), remplacée dans la variante [Angular] de ce cours par les signaux (signal(), computed()). [React] retient une troisième approche, plus simple en apparence : un composant re-exécute entièrement sa fonction (et donc recalcule tout son [JSX]) chaque fois que l’une de ses valeurs [useState] change - [React] compare ensuite le résultat au rendu précédent et ne modifie dans le vrai DOM du navigateur que ce qui a réellement changé (une technique appelée réconciliation, ou « DOM virtuel »). Contrairement aux signaux, un état [useState] ne « sait » pas lui-même qui dépend de lui : c’est le mécanisme de re-rendu du composant tout entier qui s’en charge, plus simple à raisonner mais - en théorie - un peu plus coûteux sur de très grandes applications, un compromis délibéré de [React].

Fonction
Rôle
useState(valeurInitiale)
crée un état local et renvoie la paire [valeur, setValeur] ; setValeur redessine le composant avec la nouvelle valeur
setValeur(nouvelleValeur)
remplace la valeur de l’état par une valeur déjà calculée
setValeur(v => ...)
calcule la nouvelle valeur à partir de l’ancienne (cf. incrementer() ci-dessus)

useMemo(() => ..., [deps])
recalcule une valeur dérivée seulement quand l’un des éléments de [deps] change - l’équivalent, en esprit, de computed() côté [Angular]

Deux mécanismes complètent [useState] pour faire communiquer un composant avec son parent - on les retrouvera constamment dans le client [RdvMedecins] (chapitre suivant), exactement à la place qu’occupaient input()/output() côté [Angular] :

  • les props : un composant [React] reçoit ses entrées comme de simples paramètres de sa fonction (un objet déstructuré, { medecins, onRechercher }) - l’équivalent direct de input.required<Medecin[]>() côté [Angular], et, en esprit, d’un attribut de directive AngularJS 1.x ;
  • les callback props : une fonction fournie par le parent (onRechercher: (criteres) => void), transmise en prop puis appelée par l’enfant au bon moment - l’équivalent direct de output<...>() (et de sa méthode .emit(valeur)) côté [Angular], et des événements personnalisés ($emit) d’AngularJS 1.x.

5.6. Pas de modules : chaque fichier est déjà autonome

Une application AngularJS 1.x s’organisait en modules (angular.module(...)). [Angular] a d’abord repris cette idée sous une forme plus stricte (les NgModule), avant de proposer les composants standalone comme mode par défaut - une simplification que la variante [Angular] de ce cours a déjà adoptée. [React] n’a jamais eu besoin de cette notion : un fichier .tsx exporte un composant avec le mot-clé export du langage JavaScript lui-même, et un autre fichier l’utilise avec un simple import - les modules ECMAScript standard, sans aucune couche supplémentaire propre à la bibliothèque d’interface :

// Parent.tsx

import { Compteur } from './Compteur';

export function Parent() {
  return (
    <div>
      <Compteur />
    </div>
  );
}

C’est le style utilisé dans tout le client [RdvMedecins] (chapitre suivant) : aucun fichier *.module.ts, aucun décorateur imports: [...] à tenir à jour - une simplification supplémentaire par rapport à AngularJS 1.x, à [Angular] avec NgModule, et même aux composants standalone d’[Angular] (qui, eux, listent malgré tout leurs dépendances dans imports: [...] du décorateur [@Component]).

5.7. Partager de la logique : hooks personnalisés et [Context]

Comme en [NestJS] (chapitre 2), le client [RdvMedecins] a besoin de logique partagée entre plusieurs composants : parler au serveur, connaître l’utilisateur connecté, changer de langue. [Angular] résout ce besoin avec des services injectables ([@Injectable]({ providedIn: 'root' }) + inject(...)) - l’héritier direct de .service()/.factory() d’AngularJS 1.x. [React] n’a pas de mécanisme d’injection de dépendances intégré ; l’usage moderne combine deux outils déjà présentés plus haut :

  • un hook personnalisé - une fonction dont le nom commence par use, qui peut elle-même appeler d’autres hooks ([useState], [useContext]…) - regroupe une logique réutilisable, exactement comme une méthode de service côté [Angular] ;
  • le Context ([React.createContext]) partage une valeur (état + fonctions) à tout un sous-arbre de composants sans avoir à la faire redescendre manuellement, prop par prop, à travers chaque niveau intermédiaire - le rôle que jouait providedIn: 'root' côté [Angular] : une seule instance, disponible partout où elle est nécessaire.

Un petit exemple, indépendant de l’étude de cas, équivalent en esprit au [MonService] présenté dans la variante [Angular] de ce cours :

// mon-contexte.tsx
import { createContext, useContext, type ReactNode } from 'react';

interface MonContexteValue {
  saluer: () => string;
}

const MonContexte = createContext<MonContexteValue | null>(null);

export function MonProvider({ children }: { children: ReactNode }) {
  const value: MonContexteValue = { saluer: () => 'Bonjour' };
  return <MonContexte.Provider value={value}>{children}</MonContexte.Provider>;
}

export function useMonContexte(): MonContexteValue {
  const contexte = useContext(MonContexte);
  if (contexte === null) {
    throw new Error('useMonContexte() doit être appelé sous un <MonProvider>');
  }
  return contexte;
}

Commentons ce code :

  • ligne 8 : const MonContexte = createContext<MonContexteValue | null>(null); — crée le Context lui-même ; null est la valeur par défaut, utilisée uniquement si aucun <MonProvider> ne se trouve au-dessus dans l’arbre - un cas d’erreur de programmation, détecté ligne 17 ;
  • lignes 10-13 : <MonContexte.Provider value={value}>{children}</MonContexte.Provider> — le composant Provider rend la valeur disponible à tous ses descendants ({children}) ; c’est lui qu’on place, une seule fois, près de la racine de l’application (cf. main.tsx du client [RdvMedecins], chapitre suivant) - l’équivalent de « l’enregistrement » implicite qu’effectuait providedIn: 'root' côté [Angular] ;
  • lignes 15-21 : export function useMonContexte(): MonContexteValue { — le hook personnalisé qui donne accès à la valeur du Context ; c’est lui que les composants appellent (const mon = useMonContexte();), exactement comme ils auraient appelé inject(MonService) côté [Angular].

5.8. Communiquer avec le serveur : [fetch]

[HttpClient] ([Angular]) était l’évolution directe du service $http d’AngularJS 1.x utilisé par le document original. [React], n’étant qu’une bibliothèque d’interface, ne fournit aucun client HTTP : le client [RdvMedecins] utilise directement [fetch], l’API standard du navigateur (disponible sans aucune dépendance supplémentaire) :

1
2
3
const reponseHttp = await fetch('http://localhost:8080/bonjour');
const reponse = (await reponseHttp.json()) as { message: string };
console.log(reponse.message);

Une différence importante avec $http et [HttpClient] : [fetch] renvoie directement une Promise (le mécanisme d’asynchronisme standard de JavaScript), pas un Observable - pas de bibliothèque RxJS, pas de .subscribe({ next, error }), pas de .pipe(map(...)). On peut l’utiliser avec .then(...), ou, plus lisible, avec async/await (comme ci-dessus, qui suspend l’exécution de la fonction jusqu’à ce que la promesse se résolve, sans bloquer le reste de l’application). Une erreur réseau ou une réponse en erreur se traite alors avec un simple try/catch, plutôt qu’avec la branche error d’un abonnement RxJS.

Le hook useRdvService() du client [RdvMedecins] (chapitre suivant) encapsule systématiquement cet appel [fetch] suivi de .json(), pour un motif précis expliqué à ce moment-là (l’enveloppe [Reponse]<T> commune à toutes les réponses du serveur).

5.9. Le rendu conditionnel et les listes : du [JSX] pur

AngularJS 1.x utilisait des directives structurelles (ng-if, ng-repeat) pour afficher un élément conditionnellement ou répéter un bloc de template pour chaque élément d’une liste. La variante [Angular] de ce cours utilise la syntaxe intégrée [@if]/[@for]. [React] n’a pas de syntaxe de template séparée à apprendre : le [JSX] retourné par un composant EST du JavaScript, et le contrôle de flux s’exprime avec les opérateurs JavaScript ordinaires :

{utilisateurConnecte ? (
  <p>Bienvenue !</p>
) : (
  <p>Veuillez vous connecter.</p>
)}

<ul>
  {listeItems.map((item) => (
    <li key={item.id}>{item.nom}</li>
  ))}
</ul>

Commentons ce code :

  • lignes 1-5 : utilisateurConnecte ? (...) : (...) — l’opérateur ternaire de JavaScript remplace [@if]/[@else] ([Angular]) et ng-if (AngularJS 1.x) : la condition choisit laquelle des deux expressions [JSX] est retournée. Pour une condition sans alternative, l’opérateur && suffit : {condition && <p>...</p>} n’affiche rien du tout si condition vaut false (c’est le style utilisé, par exemple, par le bandeau ADMIN/USER du composant App du client [RdvMedecins], chapitre suivant) ;
  • ligne 8 : listeItems.map((item) => (...)) — .map() (une méthode standard des tableaux JavaScript) remplace [@for] ([Angular]) et ng-repeat (AngularJS 1.x) : elle transforme chaque élément du tableau en un élément [JSX] ;
  • ligne 9 : key={item.id} (obligatoire dans une liste [JSX]) — indique à [React] comment reconnaître un élément déjà affiché lorsqu’il redessine la liste, exactement le même rôle que track item.id après [@for] côté [Angular].

5.10. Récapitulatif : correspondances [Angular] -> [React]

[Angular] (variante précédente de ce cours)
[React] (2026)

Composants standalone ([@Component])
Composants fonctionnels
Signaux (signal(), input(), output(), computed())
[useState](), props, fonctions de rappel en prop, [useMemo]()
effect()
[useEffect]()
Template séparé (.html) + décorateur (.ts)
[JSX] intégré au composant (.tsx)

[@if], [@for]
Opérateur ternaire / &&, .map()

Services injectables ([@Injectable]({ providedIn: 'root' })) + inject()
Hooks personnalisés + [Context]

[HttpClient] (Observables RxJS)
[fetch] (Promises natives)

[@angular/router] (table de routage vide)
Aucun routeur (même conclusion, poussée plus loin)

Angular CLI / [@angular/build]
[Vite]

Ces notions suffisent pour aborder le client [RdvMedecins] du chapitre suivant : chaque nouvelle utilisation y sera malgré tout réexpliquée, au fil du code, à l’endroit précis où elle apparaît.