Cours JavaScript et React · Module 8: Patterns React avancés

Performance Profiling

13 min de lecture
Dans cette leçon7

Notre voyage cosmique à travers React touche à sa fin, mais avant de partir pour de nouvelles explorations, nous devons nous assurer que notre vaisseau fonctionne à pleine capacité. Dans ce module, nous nous concentrerons sur le profiling des performances des applications React - c'est-à-dire l'identification et l'analyse des « goulots d'étranglement » dans notre code.

Qu'est-ce que le profiling des performances ?

Le profiling des performances est le processus consistant à identifier les endroits d'une application qui consomment le plus de ressources (temps CPU, mémoire) et peuvent entraîner un comportement lent de l'interface utilisateur. C'est comme diagnostiquer les moteurs d'un vaisseau spatial pour vérifier quelles parties ne fonctionnent pas efficacement et peuvent être optimisées.

Dans le contexte des applications React, le profiling te permet de :

  • Identifier les composants qui se rendent trop souvent
  • Localiser les opérations longues
  • Comprendre les patterns de rendu
  • Détecter les mises à jour d'état inutiles
  • Pointer les zones nécessitant une optimisation

Outils de profiling React

1. React DevTools Profiler

React DevTools est une extension de navigateur qui permet d'analyser les performances des applications React. Elle inclut un onglet « Profiler », qui est l'outil le plus simple et le plus puissant pour profiler les applications React.

Comment utiliser le React DevTools Profiler :

  1. Installer React DevTools

  2. Démarrer le profiling :

    • Ouvre les outils de développement du navigateur
    • Va dans l'onglet « Profiler » de React DevTools
    • Clique sur le bouton d'enregistrement (cercle rouge)
    • Effectue les actions que tu veux profiler dans l'application
    • Arrête l'enregistrement
  3. Analyser les résultats :

    • Flame Chart - montre la hiérarchie des composants rendus et leur temps de rendu
    • Ranked Chart - trie les composants par temps de rendu
    • Component Chart - montre quels composants ont été re-rendus à chaque mise à jour

Interpréter les couleurs dans le Profiler :

  • Gris - le composant ne s'est pas rendu pendant la mise à jour
  • Jaune/Orange/Rouge - le composant s'est rendu, et la couleur indique combien de temps cela a pris (plus c'est rouge, plus c'est long)
  • Noir - le composant était la cause de la mise à jour (par exemple, en changeant l'état)

2. Chrome Performance Panel

Chrome DevTools inclut un panneau Performance qui permet un profiling plus général des performances de la page :

  1. Démarrer le profiling :

    • Ouvre DevTools dans Chrome (F12)
    • Va dans l'onglet « Performance »
    • Clique sur le bouton d'enregistrement
    • Effectue des actions dans l'application
    • Arrête l'enregistrement
  2. Analyser les résultats :

    • Main - montre l'activité sur le thread principal (où se déroule la plupart du travail JS)
    • Frames - montre les images par seconde (FPS)
    • Network - montre l'activité réseau
    • Memory - montre l'utilisation mémoire

Ce panneau est particulièrement utile pour identifier :

  • Les scripts longs qui bloquent le thread principal
  • Les opérations DOM excessives
  • Les problèmes de performance de rendu

3. API de profiling intégrée à React

React fournit une API intégrée pour le profiling en mode développement :

1// Importer le composant Profiler
2import { Profiler } from 'react';
3
4// Fonction de callback appelée après le rendu
5function onRenderCallback(
6  id, // identifiant de l'arbre de composants profilé
7  phase, // "mount" (premier rendu) ou "update" (re-render)
8  actualDuration, // temps passé à rendre
9  baseDuration, // temps de rendu estimé de tout le sous-arbre sans optimisations
10  startTime, // quand React a commencé à rendre
11  commitTime, // quand React a fini de rendre
12  interactions // ensemble des « interactions » suivies pour ce rendu
13) {
14  console.log({
15    id,
16    phase,
17    actualDuration,
18    baseDuration,
19    startTime,
20    commitTime
21  });
22}
23
24// Utilisation du composant Profiler dans l'application
25function App() {
26  return (
27    <Profiler id="MissionControl" onRender={onRenderCallback}>
28      <MissionControl />
29    </Profiler>
30  );
31}

Tu peux imbriquer des composants Profiler pour profiler différentes parties de l'application :

1function Dashboard() {
2  return (
3    <Profiler id="Dashboard" onRender={onRenderCallback}>
4      <h1>Panneau de contrôle de mission</h1>
5
6      <Profiler id="Navigation" onRender={onRenderCallback}>
7        <Navigation />
8      </Profiler>
9
10      <Profiler id="Stats" onRender={onRenderCallback}>
11        <MissionStats />
12      </Profiler>
13
14      <Profiler id="Crew" onRender={onRenderCallback}>
15        <CrewStatus />
16      </Profiler>
17    </Profiler>
18  );
19}

Cette méthode de profiling est particulièrement utile quand :

  • Tu veux collecter des données de performance dans un environnement de test
  • Tu as des composants spécifiques que tu veux surveiller
  • Tu veux automatiser la collecte de données de performance

Problèmes de performance courants et comment les profiler

1. Composants qui se rendent trop fréquemment

Problème : Les composants se re-rendent même quand leurs données n'ont pas changé.

Comment profiler :

  • Utilise le React DevTools Profiler pour voir quels composants se rendent trop souvent
  • Vérifie si les composants parents provoquent des rendus inutiles des enfants

Exemple du problème :

1function ParentComponent() {
2  const [count, setCount] = useState(0);
3
4  // Cette fonction est recréée à chaque rendu
5  const handleClick = () => {
6    console.log('Button clicked');
7  };
8
9  return (
10    <div>
11      <button onClick={() => setCount(count + 1)}>
12        Augmenter le compteur : {count}
13      </button>
14
15      {/* ChildComponent se re-rendra à chaque changement de count,
16          même si handleClick a la même fonctionnalité */}
17      <ChildComponent onClick={handleClick} />
18    </div>
19  );
20}

Solution :

1function ParentComponent() {
2  const [count, setCount] = useState(0);
3
4  // La mémoïsation de la fonction empêche de créer une nouvelle référence à chaque rendu
5  const handleClick = useCallback(() => {
6    console.log('Button clicked');
7  }, []);
8
9  return (
10    <div>
11      <button onClick={() => setCount(count + 1)}>
12        Augmenter le compteur : {count}
13      </button>
14
15      {/* ChildComponent enveloppé dans React.memo ne se re-rendra pas
16          quand ses props ne changent pas */}
17      <MemoizedChildComponent onClick={handleClick} />
18    </div>
19  );
20}
21
22const MemoizedChildComponent = React.memo(ChildComponent);

2. Calculs lourds pendant le rendu

Problème : Effectuer des calculs complexes pendant le rendu provoque des ralentissements de l'UI.

Comment profiler :

  • Observe dans le React DevTools Profiler quels composants ont le temps de rendu le plus long
  • Utilise le Chrome Performance Panel pour voir les longues tâches JavaScript

Exemple du problème :

1function DataAnalytics({ data }) {
2  // Calculs lourds effectués à chaque rendu
3  const processedData = processLargeDataset(data);
4  const statistics = calculateStatistics(processedData);
5
6  return (
7    <div>
8      <h2>Analyse des données de mission</h2>
9      <DataVisualisation stats={statistics} />
10    </div>
11  );
12}

Solution :

1function DataAnalytics({ data }) {
2  // Mémoïsation des calculs lourds - ils ne s'exécutent que lorsque data change
3  const processedData = useMemo(() => {
4    return processLargeDataset(data);
5  }, [data]);
6
7  const statistics = useMemo(() => {
8    return calculateStatistics(processedData);
9  }, [processedData]);
10
11  return (
12    <div>
13      <h2>Analyse des données de mission</h2>
14      <DataVisualisation stats={statistics} />
15    </div>
16  );
17}

3. Mises à jour d'état excessives

Problème : Des mises à jour d'état trop fréquentes provoquent des re-rendus en cascade.

Comment profiler :

  • Dans le React DevTools Profiler, trouve les composants marqués en noir (initiateurs de mise à jour)
  • Observe les patterns de mise à jour - certains composants se mettent-ils à jour en groupes ?

Exemple du problème :

1function SpaceshipSensors() {
2  const [temperature, setTemperature] = useState(0);
3  const [pressure, setPressure] = useState(0);
4  const [oxygen, setOxygen] = useState(0);
5  const [radiation, setRadiation] = useState(0);
6
7  // Récupération des données des capteurs toutes les 100ms
8  useEffect(() => {
9    const interval = setInterval(() => {
10      // Chaque mise à jour provoque un rendu distinct
11      setTemperature(getTemperature());
12      setPressure(getPressure());
13      setOxygen(getOxygen());
14      setRadiation(getRadiation());
15    }, 100);
16
17    return () => clearInterval(interval);
18  }, []);
19
20  return (
21    <div>
22      <h2>Relevés des capteurs</h2>
23      <p>Température : {temperature}°C</p>
24      <p>Pression : {pressure} hPa</p>
25      <p>Niveau d'oxygène : {oxygen}%</p>
26      <p>Niveau de radiation : {radiation} μSv/h</p>
27    </div>
28  );
29}

Solution :

1function SpaceshipSensors() {
2  // Stocker les données liées ensemble dans un seul objet d'état
3  const [sensorData, setSensorData] = useState({
4    temperature: 0,
5    pressure: 0,
6    oxygen: 0,
7    radiation: 0
8  });
9
10  useEffect(() => {
11    const interval = setInterval(() => {
12      // Une seule mise à jour d'état au lieu de quatre
13      setSensorData({
14        temperature: getTemperature(),
15        pressure: getPressure(),
16        oxygen: getOxygen(),
17        radiation: getRadiation()
18      });
19    }, 100);
20
21    return () => clearInterval(interval);
22  }, []);
23
24  return (
25    <div>
26      <h2>Relevés des capteurs</h2>
27      <p>Température : {sensorData.temperature}°C</p>
28      <p>Pression : {sensorData.pressure} hPa</p>
29      <p>Niveau d'oxygène : {sensorData.oxygen}%</p>
30      <p>Niveau de radiation : {sensorData.radiation} μSv/h</p>
31    </div>
32  );
33}

4. Grandes listes et visualisations de données

Problème : Le rendu de longues listes ou de visualisations complexes peut ralentir l'interface.

Comment profiler :

  • Utilise le React DevTools Profiler pour identifier les composants de liste qui mettent longtemps à se rendre
  • Vérifie à quelle fréquence les éléments individuels de la liste se rendent

Exemple d'utilisation de React DevTools pour profiler les performances d'une liste :

  1. Démarre le profiling
  2. Fais défiler la liste pour voir comment l'application répond
  3. Arrête le profiling
  4. Vérifie quels composants ont le temps de rendu le plus long dans le ranked chart

Solution :

  • Virtualisation de liste (React Window, React Virtualized)
  • Mémoïsation des éléments de liste
  • Pagination au lieu d'afficher tous les éléments d'un coup

Techniques de profiling avancées

1. User Timing API

L'API Performance du navigateur permet de créer des marqueurs de temps personnalisés :

1function ExpensiveComponent({ data }) {
2  useEffect(() => {
3    // Début de la mesure
4    performance.mark('expensive-calculation-start');
5
6    // Effectuer des calculs coûteux
7    const result = performExpensiveCalculation(data);
8
9    // Fin de la mesure
10    performance.mark('expensive-calculation-end');
11
12    // Créer une mesure entre les marqueurs
13    performance.measure(
14      'expensive-calculation',
15      'expensive-calculation-start',
16      'expensive-calculation-end'
17    );
18
19    // Afficher les résultats
20    console.log(performance.getEntriesByName('expensive-calculation'));
21  }, [data]);
22
23  //
24}

Ces marqueurs seront visibles dans le panneau Performance de Chrome DevTools.

2. Surveillance des performances en production

Pour surveiller les performances en production, tu peux utiliser :

  • Web Vitals - une bibliothèque Google pour mesurer les indicateurs de performance clés
  • New Relic, Datadog, Sentry - outils APM (Application Performance Monitoring)
  • Des solutions personnalisées utilisant l'API Performance et l'API Profiler

Exemple de collecte de données depuis le React Profiler et envoi vers un serveur :

1function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
2  // Nous ne collectons que les données de la phase update qui prend plus de 10ms
3  if (phase === 'update' && actualDuration > 10) {
4    sendToAnalyticsServer({
5      componentId: id,
6      renderTime: actualDuration,
7      timestamp: Date.now()
8    });
9  }
10}
11
12function App() {
13  return (
14    <Profiler id="App" onRender={onRenderCallback}>
15      <AppContent />
16    </Profiler>
17  );
18}

Étude de cas : profiling et optimisation d'une application React

Analysons le processus de profiling et d'optimisation des performances d'une application React à travers l'exemple d'un panneau de contrôle de mission spatiale :

Étape 1 : Identifier le problème

Les utilisateurs signalent que le panneau de contrôle est lent, surtout lors de l'affichage des données de télémétrie et de la liste des événements.

Étape 2 : Profiler l'application

Nous utilisons le React DevTools Profiler pour identifier la source du problème :

  1. Nous enregistrons une session pendant une utilisation normale du panneau
  2. Nous analysons les résultats dans le Profiler
  3. Nous découvrons que :
    • Le composant TelemetryChart se rend très lentement (>100ms)
    • Le composant EventsList se re-rend chaque fois que de nouvelles données de télémétrie arrivent
    • La fonction calculateTrends consomme beaucoup de temps CPU

Étape 3 : Optimisation

Sur la base des résultats du profiling, nous introduisons les optimisations suivantes :

  1. Optimiser TelemetryChart :
1// Avant optimisation
2function TelemetryChart({ data }) {
3  // Traitement lourd des données à chaque rendu
4  const chartData = processChartData(data);
5
6  return <LineChart data={chartData} />;
7}
8
9// Après optimisation
10const TelemetryChart = React.memo(function TelemetryChart({ data }) {
11  // Traitement des données mémoïsé
12  const chartData = useMemo(() => processChartData(data), [data]);
13
14  return <LineChart data={chartData} />;
15});
  1. Optimiser EventsList :
1// Avant optimisation
2function EventsList({ events }) {
3  return (
4    <div>
5      {events.map(event => (
6        <EventItem key={event.id} event={event} />
7      ))}
8    </div>
9  );
10}
11
12// Après optimisation
13const EventsList = React.memo(function EventsList({ events }) {
14  return (
15    <div>
16      {events.map(event => (
17        <MemoizedEventItem key={event.id} event={event} />
18      ))}
19    </div>
20  );
21});
22
23const MemoizedEventItem = React.memo(EventItem);
  1. Déplacer les calculs lourds vers un web worker :
1// Avant optimisation
2function MissionControlPanel({ telemetryData }) {
3  const trends = calculateTrends(telemetryData);  // Bloque le thread principal
4
5  return (
6    <div>
7      <TrendsDisplay trends={trends} />
8      {/* autres composants */}
9    </div>
10  );
11}
12
13// Après optimisation
14function MissionControlPanel({ telemetryData }) {
15  const [trends, setTrends] = useState(null);
16
17  useEffect(() => {
18    if (!telemetryData) return;
19
20    // Créer un web worker
21    const worker = new Worker('./trendsWorker.js');
22
23    // Écouter la réponse
24    worker.addEventListener('message', (event) => {
25      setTrends(event.data);
26      worker.terminate();
27    });
28
29    // Envoyer les données pour traitement
30    worker.postMessage(telemetryData);
31  }, [telemetryData]);
32
33  return (
34    <div>
35      {trends ? <TrendsDisplay trends={trends} /> : <LoadingSpinner />}
36      {/* autres composants */}
37    </div>
38  );
39}

Étape 4 : Vérifier les optimisations

Après avoir introduit les optimisations, nous profilons à nouveau l'application :

  1. Le temps de rendu de TelemetryChart est passé de >100ms à ~15ms
  2. Le composant EventsList ne se rend plus lors des mises à jour des données de télémétrie
  3. Le thread principal n'est plus bloqué par calculateTrends

Grâce à ces optimisations, l'interface utilisateur fonctionne de façon fluide, et le panneau de contrôle répond immédiatement aux interactions de l'utilisateur.

Bonnes pratiques de profiling

  1. Profile dans un environnement proche de la production

    • Lance l'application en mode production (npm run build et sers les fichiers construits)
    • Teste sur des appareils similaires à ceux utilisés par les utilisateurs
  2. Établis des références (baselines)

    • Mesure les performances avant et après les optimisations
    • Fixe des objectifs de performance précis (par exemple, temps du premier rendu < 2s)
  3. Profile régulièrement

    • Intègre le profiling dans le cycle de développement de l'application
    • Surveille les performances après l'introduction de changements majeurs
  4. Concentre-toi sur la perception de l'utilisateur

    • Priorise les optimisations qui ont le plus d'impact sur la performance perçue
    • Souviens-toi des métriques clés Web Vitals (LCP, FID, CLS)
  5. Documente les résultats du profiling

    • Enregistre les données de performance avant et après les optimisations
    • Partage les connaissances et techniques d'optimisation avec l'équipe

Résumé

Le profiling des performances est un élément essentiel de la création d'applications React de haute qualité. Tout comme les ingénieurs spatiaux surveillent chaque aspect du fonctionnement d'un vaisseau, nous devrions analyser en continu les performances de nos applications pour garantir aux utilisateurs une expérience fluide et réactive.

Points clés à retenir :

  1. Utilise les bons outils - React DevTools Profiler, Chrome Performance Panel
  2. Identifie les goulots d'étranglement - trouve les composants qui se rendent trop souvent ou trop longtemps
  3. Applique les techniques d'optimisation appropriées - mémoïsation, éviter les rendus inutiles, virtualisation de listes
  4. Mesure l'efficacité des optimisations - compare les performances avant et après les changements
  5. Optimise avec modération - souviens-toi qu'une optimisation prématurée peut conduire à du code trop compliqué

Avec ces techniques et outils de profiling, tu seras capable de créer des applications React performantes qui offrent une excellente expérience aux utilisateurs, même dans les interfaces de panneau de contrôle cosmique les plus complexes.

Code de cette leçon : App.jsx
1import React, { useState, useMemo, useCallback, useEffect, useRef, Profiler } from 'react';
2
3// ============================================
4// PROFILING DES PERFORMANCES REACT
5// ============================================
6// Cet exemple démontre des techniques d'optimisation
7// et de profiling des composants React.
8
9// --- Callback de profiling ---
10function onRenderCallback(id, phase, actualDuration, baseDuration) {
11  console.log(
12    `[Profiler] ${id} | phase: ${phase} | actual: ${actualDuration.toFixed(2)}ms | base: ${baseDuration.toFixed(2)}ms`
13  );
14}
15
16// --- Simulation d'un calcul lourd ---
17function expensiveCalculation(items) {
18  let result = 0;
19  for (let i = 0; i < items.length; i++) {
20    for (let j = 0; j < 1000; j++) {
21      result += Math.sqrt(items[i].value * j + 1);
22    }
23  }
24  return result.toFixed(2);
25}
26
27// --- Composant SANS optimisation ---
28function UnoptimizedChild({ label, onClick }) {
29  const renderCount = useRef(0);
30  renderCount.current += 1;
31
32  return (
33    <div style={childStyle}>
34      <span>{label}</span>
35      <span style={renderBadge}>{renderCount.current}x rendu</span>
36      <button onClick={onClick} style={btnStyle}>Cliquer</button>
37    </div>
38  );
39}
40
41// --- Composant AVEC optimisation (React.memo) ---
42const OptimizedChild = React.memo(function OptimizedChild({ label, onClick }) {
43  const renderCount = useRef(0);
44  renderCount.current += 1;
45
46  return (
47    <div style={{ ...childStyle, borderColor: '#4CAF50' }}>
48      <span>{label}</span>
49      <span style={{ ...renderBadge, background: '#4CAF50' }}>{renderCount.current}x rendu</span>
50      <button onClick={onClick} style={{ ...btnStyle, background: '#4CAF50' }}>Cliquer</button>
51    </div>
52  );
53});
54
55// --- Composant de liste avec virtualisation (simplifiee) ---
56function VirtualizedList({ items, itemHeight = 40, containerHeight = 200 }) {
57  const [scrollTop, setScrollTop] = useState(0);
58  const containerRef = useRef(null);
59
60  const visibleStart = Math.floor(scrollTop / itemHeight);
61  const visibleCount = Math.ceil(containerHeight / itemHeight) + 1;
62  const visibleItems = items.slice(visibleStart, visibleStart + visibleCount);
63
64  return (
65    <div
66      ref={containerRef}
67      onScroll={(e) => setScrollTop(e.target.scrollTop)}
68      style={{
69        height: containerHeight, overflow: 'auto',
70        background: '#0d1b2a', borderRadius: '8px', border: '1px solid #2a3f5f',
71      }}
72    >
73      <div style={{ height: items.length * itemHeight, position: 'relative' }}>
74        {visibleItems.map((item, i) => (
75          <div
76            key={item.id}
77            style={{
78              position: 'absolute', top: (visibleStart + i) * itemHeight,
79              height: itemHeight, width: '100%', padding: '8px 12px',
80              boxSizing: 'border-box', borderBottom: '1px solid #1b2838',
81              color: '#e0e1dd', display: 'flex', alignItems: 'center',
82            }}
83          >
84            {item.name} - valeur : {item.value}
85          </div>
86        ))}
87      </div>
88    </div>
89  );
90}
91
92// --- Application principale ---
93function App() {
94  const [count, setCount] = useState(0);
95  const [filterText, setFilterText] = useState('');
96  const [showOptimized, setShowOptimized] = useState(false);
97
98  // Nous generons une liste de 500 elements
99  const largeList = useMemo(() => {
100    return Array.from({ length: 500 }, (_, i) => ({
101      id: i,
102      name: `Mission #${i + 1}`,
103      value: Math.floor(Math.random() * 1000),
104    }));
105  }, []);
106
107  // Filtrage avec useMemo - les calculs ne s'executent que lorsque les donnees changent
108  const filteredItems = useMemo(() => {
109    return largeList.filter((item) =>
110      item.name.toLowerCase().includes(filterText.toLowerCase())
111    );
112  }, [largeList, filterText]);
113
114  // Calcul lourd avec memoisation
115  const expensiveResult = useMemo(() => {
116    return expensiveCalculation(filteredItems.slice(0, 50));
117  }, [filteredItems]);
118
119  // Callback SANS memoisation (nouvelle reference a chaque rendu)
120  const handleClickUnoptimized = () => {
121    console.log('Clic sans mémoïsation');
122  };
123
124  // Callback AVEC memoisation (reference stable)
125  const handleClickOptimized = useCallback(() => {
126    console.log('Clic avec useCallback');
127  }, []);
128
129  return (
130    <div style={appStyle}>
131      <h1 style={{ color: '#ffd700', textAlign: 'center', marginBottom: '8px' }}>
132        Profiling des performances React
133      </h1>
134      <p style={{ textAlign: 'center', color: '#aaa', marginBottom: '20px' }}>
135        Ouvre la console du navigateur pour voir les logs du Profiler
136      </p>
137
138      {/* Section 1 : React.memo + useCallback */}
139      <Profiler id="MemoDemo" onRender={onRenderCallback}>
140        <div style={sectionStyle}>
141          <h2 style={headingStyle}>React.memo + useCallback</h2>
142          <p style={{ color: '#aaa', marginBottom: '12px' }}>
143            Clique sur "+1" et observe quel composant se re-rend.
144          </p>
145          <div style={{ textAlign: 'center', marginBottom: '16px' }}>
146            <span style={{ fontSize: '24px', marginRight: '12px' }}>Counter: {count}</span>
147            <button onClick={() => setCount((c) => c + 1)} style={btnAccent}>+1</button>
148          </div>
149
150          <div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
151            <div style={{ flex: 1, minWidth: '220px' }}>
152              <h3 style={{ color: '#ff6b6b' }}>Sans optimisation</h3>
153              <UnoptimizedChild
154                label="Composant A"
155                onClick={handleClickUnoptimized}
156              />
157            </div>
158            <div style={{ flex: 1, minWidth: '220px' }}>
159              <h3 style={{ color: '#4CAF50' }}>Avec React.memo + useCallback</h3>
160              <OptimizedChild
161                label="Composant B"
162                onClick={handleClickOptimized}
163              />
164            </div>
165          </div>
166        </div>
167      </Profiler>
168
169      {/* Section 2 : useMemo sur des calculs lourds */}
170      <Profiler id="UseMemoDemo" onRender={onRenderCallback}>
171        <div style={sectionStyle}>
172          <h2 style={headingStyle}>useMemo - calculs lourds</h2>
173          <p style={{ color: '#aaa', marginBottom: '12px' }}>
174            Le filtrage et les calculs sont mémoïsés - changer le filtre recalcule le résultat.
175          </p>
176          <input
177            value={filterText}
178            onChange={(e) => setFilterText(e.target.value)}
179            placeholder="Filtrer les missions..."
180            style={inputStyle}
181          />
182          <p style={{ margin: '8px 0', color: '#ffd700' }}>
183            Résultat du calcul : {expensiveResult} | Trouvé : {filteredItems.length} missions
184          </p>
185        </div>
186      </Profiler>
187
188      {/* Section 3 : Virtualisation de la liste */}
189      <Profiler id="VirtualizedListDemo" onRender={onRenderCallback}>
190        <div style={sectionStyle}>
191          <h2 style={headingStyle}>Virtualisation de la liste (500 éléments)</h2>
192          <p style={{ color: '#aaa', marginBottom: '12px' }}>
193            Seuls les éléments visibles sont rendus - fais défiler la liste.
194          </p>
195          <label style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px', cursor: 'pointer' }}>
196            <input
197              type="checkbox"
198              checked={showOptimized}
199              onChange={() => setShowOptimized(!showOptimized)}
200            />
201            Afficher la liste virtualisée
202          </label>
203
204          {showOptimized ? (
205            <VirtualizedList items={filteredItems} />
206          ) : (
207            <div style={{
208              height: 200, overflow: 'auto', background: '#0d1b2a',
209              borderRadius: '8px', border: '1px solid #2a3f5f',
210            }}>
211              {filteredItems.map((item) => (
212                <div key={item.id} style={{
213                  padding: '8px 12px', borderBottom: '1px solid #1b2838',
214                  color: '#e0e1dd',
215                }}>
216                  {item.name} - valeur : {item.value}
217                </div>
218              ))}
219            </div>
220          )}
221        </div>
222      </Profiler>
223
224      {/* TODO : Ajoute une section comparant les performances
225          de deux approches de mise à jour de l'état :
226          1. Plusieurs useState séparés (température, pression, oxygène)
227          2. Un seul useState avec un objet { temperature, pressure, oxygen }
228          Utilise le composant Profiler pour mesurer les différences. */}
229    </div>
230  );
231}
232
233// --- Styles ---
234const appStyle = {
235  background: '#0d1b2a', minHeight: '100vh', padding: '20px',
236  fontFamily: 'Arial, sans-serif', color: '#e0e1dd',
237};
238const sectionStyle = {
239  background: '#1b2838', borderRadius: '12px', padding: '20px',
240  maxWidth: '700px', margin: '16px auto', border: '1px solid #2a3f5f',
241};
242const headingStyle = { color: '#00d4ff', marginBottom: '8px' };
243const childStyle = {
244  display: 'flex', alignItems: 'center', gap: '10px',
245  padding: '10px', background: '#0d1b2a', borderRadius: '8px',
246  border: '1px solid #ff6b6b', marginBottom: '8px',
247};
248const renderBadge = {
249  background: '#ff6b6b', color: '#fff', padding: '2px 8px',
250  borderRadius: '12px', fontSize: '12px',
251};
252const btnStyle = {
253  marginLeft: 'auto', padding: '6px 14px', background: '#ff6b6b',
254  color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer',
255};
256const btnAccent = {
257  padding: '8px 20px', background: '#ffd700', color: '#000',
258  border: 'none', borderRadius: '8px', cursor: 'pointer', fontWeight: 'bold',
259};
260const inputStyle = {
261  width: '100%', padding: '8px 12px', background: '#0d1b2a',
262  border: '1px solid #2a3f5f', borderRadius: '6px', color: '#e0e1dd',
263  fontSize: '14px', boxSizing: 'border-box',
264};
265
266export 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. 1. Que fait React.memo() et quand faut-il l'utiliser ?

  2. 2. À quoi sert le React Profiler dans les React DevTools ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Chargement dynamique de modules

  • Tri horizontal

    Ordonne la syntaxe de Suspense avec une UI de repli (fallback) :

  • Tri vertical

    Ordonne les stratégies de test par priorité :

  • Éditeur de code

    Custom hook useForm avec validation

Articles utiles