Cours JavaScript et React · Module 10: Écosystème et avenir de React
React Compiler (React Forget)
Dans cette leçon8
Imagine que, sur ton vaisseau spatial, au lieu de configurer manuellement chaque système d'optimisation, tu disposes d'un pilote automatique intelligent qui détecte et optimise automatiquement toutes les opérations redondantes. C'est exactement ce que fait le React Compiler (aussi appelé React Forget) - il analyse automatiquement ton code et ajoute la mémoïsation là où elle est nécessaire. La version 1.0 est stable depuis octobre 2025 et fonctionne avec React 17, 18 et 19, au mieux avec la 19.
Le problème : la mémoïsation manuelle
Jusqu'à présent, l'optimisation des performances en React nécessitait l'utilisation manuelle de React.memo, useMemo et useCallback. C'est comme configurer manuellement chaque système d'un vaisseau spatial - chronophage, source d'erreurs et facile à oublier :
1// AVANT le React Compiler - mémoïsation manuelle
2import { useState, useMemo, useCallback, memo } from 'react';
3
4const MissionCard = memo(function MissionCard({ mission, onSelect }) {
5 return (
6 <div onClick={() => onSelect(mission.id)}>
7 <h3>{mission.name}</h3>
8 <p>Status: {mission.status}</p>
9 </div>
10 );
11});
12
13function MissionDashboard({ missions }) {
14 const [filter, setFilter] = useState('all');
15 const [selectedId, setSelectedId] = useState(null);
16
17 // Il faut penser à useMemo...
18 const filteredMissions = useMemo(
19 () => missions.filter(m => filter === 'all' || m.status === filter),
20 [missions, filter]
21 );
22
23 // ...et à useCallback pour chaque fonction passée en prop
24 const handleSelect = useCallback((id) => {
25 setSelectedId(id);
26 }, []);
27
28 return (
29 <div>
30 {filteredMissions.map(m => (
31 <MissionCard key={m.id} mission={m} onSelect={handleSelect} />
32 ))}
33 </div>
34 );
35}Problèmes de la mémoïsation manuelle :
- Facile à oublier - oublie un
useCallbacketReact.memone fonctionnera pas - Code répétitif - beaucoup de code sans rapport avec la logique métier
- Mauvaises dépendances - un tableau de dépendances incorrect dans
useMemo/useCallback= bug - Sur-mémoïsation - mémoïser des choses qui n'ont pas besoin d'être optimisées
React Compiler - Mémoïsation automatique
Le React Compiler est un outil qui s'exécute au moment du build et qui analyse automatiquement les composants React pour ajouter la mémoïsation appropriée. Il agit au moment de la compilation - ton code source reste propre :
1// APRÈS le React Compiler - tu écris du code simple et propre
2function MissionDashboard({ missions }) {
3 const [filter, setFilter] = useState('all');
4 const [selectedId, setSelectedId] = useState(null);
5
6 // Le compilateur mémoïse AUTOMATIQUEMENT ce calcul
7 const filteredMissions = missions.filter(
8 m => filter === 'all' || m.status === filter
9 );
10
11 // Le compilateur stabilise AUTOMATIQUEMENT cette fonction
12 const handleSelect = (id) => {
13 setSelectedId(id);
14 };
15
16 return (
17 <div>
18 {filteredMissions.map(m => (
19 <MissionCard key={m.id} mission={m} onSelect={handleSelect} />
20 ))}
21 </div>
22 );
23}
24
25// Pas besoin de React.memo - le compilateur s'en charge !
26function MissionCard({ mission, onSelect }) {
27 return (
28 <div onClick={() => onSelect(mission.id)}>
29 <h3>{mission.name}</h3>
30 <p>Status: {mission.status}</p>
31 </div>
32 );
33}Comment fonctionne le React Compiler ?
Le Compiler analyse ton code pendant le build (par exemple avec un plugin Babel) et génère une version optimisée :
1// Ton code source (ce que tu écris) :
2function StarList({ stars, highlight }) {
3 const visible = stars.filter(s => s.magnitude < 6);
4 const style = { color: highlight ? '#ffd700' : '#ccc' };
5
6 return (
7 <ul style={style}>
8 {visible.map(s => <li key={s.id}>{s.name}</li>)}
9 </ul>
10 );
11}
12
13// Ce que génère le compilateur (exemple simplifié) :
14function StarList_compiled({ stars, highlight }) {
15 // Le compilateur ajoute un cache pour le filtrage
16 const visible = useMemo(
17 () => stars.filter(s => s.magnitude < 6),
18 [stars]
19 );
20
21 // Le compilateur met en cache l'objet style
22 const style = useMemo(
23 () => ({ color: highlight ? '#ffd700' : '#ccc' }),
24 [highlight]
25 );
26
27 return (
28 <ul style={style}>
29 {visible.map(s => <li key={s.id}>{s.name}</li>)}
30 </ul>
31 );
32}Le Compiler fait cela intelligemment - il ne mémoïse pas tout, seulement les parties qui peuvent réellement se répéter avec les mêmes données.
Configurer le React Compiler
Installation avec Babel
Le compilateur est le plugin Babel babel-plugin-react-compiler, que tu installes comme dépendance de développement. Dans la configuration, il doit venir en premier dans la liste des plugins, car il a besoin du code d'origine avant que d'autres transformations ne le modifient :
1npm install -D babel-plugin-react-compiler@latest1// babel.config.js
2module.exports = {
3 plugins: [
4 // Le React Compiler doit s'exécuter en premier
5 ['babel-plugin-react-compiler', {
6 compilationMode: 'infer', // mode par défaut
7 panicThreshold: 'none', // un composant en erreur reste sans optimisation, et le build continue
8 }],
9 // ...autres plugins
10 ],
11};Dans un projet sous React 17 ou 18, ajoute l'option target: '18' (ou '17') et installe le paquet react-compiler-runtime, car le code compilé a alors besoin de cette bibliothèque. Dans Vite 8 avec @vitejs/plugin-react 6, tu ajoutes le compilateur via @rolldown/plugin-babel et le preset reactCompilerPreset :
1// vite.config.js
2import { defineConfig } from 'vite';
3import react, { reactCompilerPreset } from '@vitejs/plugin-react';
4import babel from '@rolldown/plugin-babel';
5
6export default defineConfig({
7 plugins: [
8 react(),
9 babel({
10 presets: [reactCompilerPreset()]
11 }),
12 ],
13});Installation avec Next.js
Dans Next.js 16, tu installes le même plugin comme dépendance de développement et tu actives l'option reactCompiler au premier niveau de la configuration. Dans les versions précédentes de Next.js, elle se trouvait dans la section experimental. Next.js n'exécute le plugin que pour les fichiers contenant du JSX ou des hooks, donc le build ne ralentit que légèrement :
1// next.config.js
2const nextConfig = {
3 reactCompiler: true,
4};
5
6module.exports = nextConfig;Tu peux aussi activer le compilateur progressivement : reactCompiler: { compilationMode: 'annotation' } ne compile que les composants dotés de la directive 'use memo' au début de la fonction, et la directive 'use no memo' désactive la compilation d'un composant choisi.
Modes de compilation
infer(par défaut) - compile les fonctions nommées comme des composants (PascalCase) ou des hooks (use...) qui créent du JSX ou appellent d'autres hooks, ainsi que les fonctions dotées de la directive'use memo'annotation- ne compile que les fonctions marquées avec la directive'use memo', ce qui facilite une adoption progressiveall- compile toutes les fonctions de premier niveau, y compris celles qui ne sont pas des composants, c'est pourquoi ce mode n'est pas recommandé
Les Rules of React
Le React Compiler ne fonctionne correctement QUE si ton code respecte les « Rules of React » :
1// BIEN - fonction pure, sans effets de bord pendant le rendu
2function GoodComponent({ data }) {
3 const sorted = [...data].sort((a, b) => a.name.localeCompare(b.name));
4 return <ul>{sorted.map(d => <li key={d.id}>{d.name}</li>)}</ul>;
5}
6
7// MAL - mutation des props (le compilateur peut générer du code incorrect !)
8function BadComponent({ data }) {
9 data.sort((a, b) => a.name.localeCompare(b.name)); // Mutation !
10 return <ul>{data.map(d => <li key={d.id}>{d.name}</li>)}</ul>;
11}
12
13// MAL - effet de bord pendant le rendu
14function BadComponent2({ userId }) {
15 // Le compilateur supposera que c'est pur et pourra l'ignorer !
16 analytics.track('render', userId);
17 return <div>User: {userId}</div>;
18}Règles clés :
- Les composants doivent être des fonctions pures - même entrée = même sortie
- Ne mute pas les props ni l'état directement
- Les effets de bord uniquement dans
useEffect, les gestionnaires d'événements ou les callbacks - Ne lis pas les refs pendant le rendu (uniquement dans les effets et les gestionnaires)
Plugin ESLint
Les règles du compilateur font désormais partie du paquet officiel eslint-plugin-react-hooks (depuis la version 6, actuellement 7.x), à côté de rules-of-hooks et exhaustive-deps. Tu l'installes avec npm install -D eslint-plugin-react-hooks@latest. Le preset recommended en configuration flat active toutes les règles recommandées, et tu règles le niveau d'une règle précise dans la section rules :
1// eslint.config.js
2import reactHooks from 'eslint-plugin-react-hooks';
3import { defineConfig } from 'eslint/config';
4
5export default defineConfig([
6 {
7 // fichiers .js et .jsx avec le parseur JSX (sans cela, ESLint 9 ignore les fichiers .jsx)
8 files: ['**/*.{js,jsx}'],
9 languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } },
10 },
11 reactHooks.configs.flat.recommended,
12 {
13 rules: {
14 'react-hooks/purity': 'error', // rendu sans effets de bord, par exemple sans Math.random()
15 'react-hooks/refs': 'error', // ne pas lire ni écrire ref.current pendant le rendu
16 'react-hooks/immutability': 'error', // ne pas muter les props ni l'état
17 },
18 },
19]);L'ancien paquet eslint-plugin-react-compiler avec la règle 'react-compiler/react-compiler' a été remplacé par ces règles, donc ne l'ajoute pas aux nouveaux projets. Le linter montre aussi quels composants le compilateur ignore et pourquoi.
Migrer du code existant
Si tu as un projet existant avec de la mémoïsation manuelle, tu peux adopter le compilateur progressivement, par exemple en mode annotation. Les React.memo, useMemo et useCallback existants n'entrent pas en conflit avec le compilateur. La documentation de React recommande de les laisser en place ou de ne les retirer qu'après des tests, car leur suppression peut changer le résultat de la compilation. Dans le nouveau code, appuie-toi sur le compilateur et garde useMemo et useCallback comme porte de sortie quand tu as besoin d'un contrôle précis, par exemple sur une dépendance d'effet.
Récapitulatif
Le React Compiler 1.0 change la façon d'optimiser React :
- Mémoïsation automatique - le Compiler mémorise les résultats à ta place, comme si tu avais ajouté
useMemo,useCallbacketReact.memolà où il faut - Code plus propre - tu écris un code plus simple et plus facile à maintenir
- Moins de bugs - le Compiler n'oubliera pas le tableau de dépendances
- Au moment du build - les optimisations sont créées à la compilation, et il ne reste à l'exécution qu'un cache léger des résultats
- Nécessite un code propre - les composants doivent respecter les Rules of React
Souviens-toi : le React Compiler est comme un pilote automatique sur un vaisseau spatial - il fait son travail à la perfection, mais exige que le vaisseau soit correctement construit.
Code de cette leçon : App.jsx
1import React, { useState, useMemo, useCallback, memo } from 'react';
2
3// === REACT COMPILER - Simulation de la mémoïsation automatique ===
4// Comparaison : mémoïsation manuelle et code « après compilation »
5
6// Compteur de rendus : un effet de bord volontaire dans le rendu, uniquement pour la mesure.
7// Dans une vraie appli, c’est le Profiler des React DevTools qui mesure les rendus.
8let renderLog = {};
9function trackRender(name) {
10 renderLog[name] = (renderLog[name] || 0) + 1;
11 return renderLog[name];
12}
13
14// === VERSION 1 : SANS mémoïsation (code naïf) ===
15function NaiveMissionCard({ mission, onSelect }) {
16 const count = trackRender('Naive-' + mission.id);
17 return (
18 <div style={{ ...styles.card, borderColor: 'rgba(255,107,107,0.3)' }}>
19 <div style={styles.renderBadge('#ff6b6b')}>R: {count}</div>
20 <strong>{mission.name}</strong>
21 <span style={styles.status(mission.status)}>{STATUS_LABELS[mission.status]}</span>
22 <button onClick={() => onSelect(mission.id)} style={styles.selectBtn}>Sélectionner</button>
23 </div>
24 );
25}
26
27// === VERSION 2 : mémoïsation MANUELLE (React.memo + useCallback + useMemo) ===
28const ManualMissionCard = memo(function ManualMissionCard({ mission, onSelect }) {
29 const count = trackRender('Manual-' + mission.id);
30 return (
31 <div style={{ ...styles.card, borderColor: 'rgba(0,212,255,0.3)' }}>
32 <div style={styles.renderBadge('#00d4ff')}>R: {count}</div>
33 <strong>{mission.name}</strong>
34 <span style={styles.status(mission.status)}>{STATUS_LABELS[mission.status]}</span>
35 <button onClick={() => onSelect(mission.id)} style={styles.selectBtn}>Sélectionner</button>
36 </div>
37 );
38});
39
40// === VERSION 3 : « compilée » (simulation du React Compiler) ===
41// Dans un vrai projet, le compilateur générerait cette mémoïsation
42const CompiledMissionCard = memo(function CompiledMissionCard({ mission, onSelect }) {
43 const count = trackRender('Compiled-' + mission.id);
44 return (
45 <div style={{ ...styles.card, borderColor: 'rgba(0,255,136,0.3)' }}>
46 <div style={styles.renderBadge('#00ff88')}>R: {count}</div>
47 <strong>{mission.name}</strong>
48 <span style={styles.status(mission.status)}>{STATUS_LABELS[mission.status]}</span>
49 <button onClick={() => onSelect(mission.id)} style={styles.selectBtn}>Sélectionner</button>
50 </div>
51 );
52});
53
54const STATUS_LABELS = { active: 'active', planned: 'planifiée', completed: 'terminée' };
55
56// Données de test
57const MISSIONS = [
58 { id: 1, name: 'Alpha Centauri', status: 'active' },
59 { id: 2, name: 'Europa Probe', status: 'planned' },
60 { id: 3, name: 'Mars Colony', status: 'completed' },
61 { id: 4, name: 'Titan Explorer', status: 'active' },
62];
63
64function App() {
65 const [counter, setCounter] = useState(0);
66 const [selected, setSelected] = useState(null);
67 const [filter, setFilter] = useState('all');
68
69 // NAÏF : nouvelle fonction et filtrage à chaque rendu
70 const naiveFiltered = MISSIONS.filter(m => filter === 'all' || m.status === filter);
71 const naiveSelect = (id) => setSelected(id);
72
73 // MANUEL : useMemo + useCallback
74 const manualFiltered = useMemo(
75 () => MISSIONS.filter(m => filter === 'all' || m.status === filter),
76 [filter]
77 );
78 const manualSelect = useCallback((id) => setSelected(id), []);
79
80 // « COMPILÉ » : identique au manuel, mais ajouté automatiquement par le compilateur
81 const compiledFiltered = useMemo(
82 () => MISSIONS.filter(m => filter === 'all' || m.status === filter),
83 [filter]
84 );
85 const compiledSelect = useCallback((id) => setSelected(id), []);
86
87 return (
88 <div style={styles.container}>
89 <header style={styles.header}>
90 <h1 style={styles.title}>React Compiler - comparaison</h1>
91 <p style={styles.subtitle}>Clique sur « Re-render » et observe les compteurs de rendus</p>
92 </header>
93
94 <div style={styles.controls}>
95 <select value={filter} onChange={e => setFilter(e.target.value)} style={styles.select}>
96 <option value="all">Toutes</option>
97 <option value="active">Actives</option>
98 <option value="planned">Planifiées</option>
99 <option value="completed">Terminées</option>
100 </select>
101 <button onClick={() => setCounter(c => c + 1)} style={styles.reRenderBtn}>
102 Re-render ({counter})
103 </button>
104 {selected && <span style={styles.selectedInfo}>ID sélectionné : {selected}</span>}
105 </div>
106
107 <div style={styles.grid}>
108 <div style={styles.column}>
109 <h3 style={{ ...styles.colTitle, color: '#ff6b6b' }}>Sans mémoïsation</h3>
110 <p style={styles.colDesc}>Chaque re-render = nouvelle fonction + nouveau filtrage</p>
111 {naiveFiltered.map(m => (
112 <NaiveMissionCard key={m.id} mission={m} onSelect={naiveSelect} />
113 ))}
114 </div>
115
116 <div style={styles.column}>
117 <h3 style={{ ...styles.colTitle, color: '#00d4ff' }}>Mémoïsation manuelle</h3>
118 <p style={styles.colDesc}>React.memo + useCallback + useMemo</p>
119 {manualFiltered.map(m => (
120 <ManualMissionCard key={m.id} mission={m} onSelect={manualSelect} />
121 ))}
122 </div>
123
124 <div style={styles.column}>
125 <h3 style={{ ...styles.colTitle, color: '#00ff88' }}>React Compiler</h3>
126 <p style={styles.colDesc}>Mémoïsation ajoutée automatiquement</p>
127 {compiledFiltered.map(m => (
128 <CompiledMissionCard key={m.id} mission={m} onSelect={compiledSelect} />
129 ))}
130 </div>
131 </div>
132 </div>
133 );
134}
135
136const styles = {
137 container: { fontFamily: 'system-ui', background: '#0a0e17', color: '#e0e1dd', minHeight: '100vh', padding: '14px' },
138 header: { textAlign: 'center', marginBottom: '12px' },
139 title: { fontSize: '20px', color: '#00d4ff', margin: '0 0 4px' },
140 subtitle: { fontSize: '12px', color: '#8892b0', margin: 0 },
141 controls: { display: 'flex', gap: '8px', justifyContent: 'center', alignItems: 'center', marginBottom: '14px', flexWrap: 'wrap' },
142 select: { padding: '5px 10px', background: '#1a1f2e', color: '#e0e1dd', border: '1px solid rgba(0,212,255,0.3)', borderRadius: '4px', fontSize: '12px' },
143 reRenderBtn: { padding: '5px 14px', background: 'rgba(255,107,107,0.15)', color: '#ff6b6b', border: '1px solid rgba(255,107,107,0.3)', borderRadius: '4px', cursor: 'pointer', fontSize: '12px' },
144 selectedInfo: { fontSize: '12px', color: '#ffd700' },
145 grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(190px, 1fr))', gap: '10px' },
146 column: { background: 'rgba(0,0,0,0.2)', borderRadius: '8px', padding: '10px' },
147 colTitle: { fontSize: '13px', margin: '0 0 4px' },
148 colDesc: { fontSize: '10px', color: '#556070', margin: '0 0 8px' },
149 card: { display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 8px', background: 'rgba(0,0,0,0.3)', borderRadius: '4px', marginBottom: '4px', fontSize: '11px', border: '1px solid', position: 'relative' },
150 renderBadge: (color) => ({ position: 'absolute', top: '2px', right: '4px', fontSize: '9px', color, fontWeight: 'bold' }),
151 status: (s) => ({ padding: '1px 5px', borderRadius: '6px', fontSize: '9px', background: s === 'active' ? 'rgba(0,255,136,0.15)' : s === 'completed' ? 'rgba(0,212,255,0.15)' : 'rgba(255,170,0,0.15)', color: s === 'active' ? '#00ff88' : s === 'completed' ? '#00d4ff' : '#ffaa00' }),
152 selectBtn: { marginLeft: 'auto', padding: '2px 6px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '3px', color: '#8892b0', cursor: 'pointer', fontSize: '10px' },
153};
154
155export default App;Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Que fait automatiquement le compilateur React lors de la compilation ?
2. Quelle pratique ENFREINT les règles de React et peut provoquer des erreurs dans le compilateur React ?
Tâches pratiques dans le jeu
- Clique dans l'ordre
Compose la configuration du compilateur React dans next.config.js (Next.js 16) :
- Tri horizontal
Compose la règle ESLint qui vérifie la pureté des composants - une des règles du compilateur React dans eslint-plugin-react-hooks :