Cours JavaScript et React · Module 1: Bases de React

Le Virtual DOM et ses avantages

5 min de lecture
Dans cette leçon6

L'une des fonctionnalités les plus innovantes de React est le concept de Virtual DOM (Document Object Model). Pour comprendre son importance, nous devons d'abord comprendre le problème qu'il résout.

Le problème du DOM traditionnel

Imagine le DOM du navigateur comme une immense carte stellaire, où chaque étoile, planète et station spatiale doit être placée avec précision. Chaque modification de cette carte a un coût : le navigateur doit recalculer la position des objets concernés par le changement.

Dans l'approche traditionnelle, lorsqu'un utilisateur effectue une action (par exemple, clique sur un bouton), c'est à toi de :

  1. Trouver dans le DOM tous les éléments concernés par le changement
  2. Modifier chacun d'eux à la main - ou, par facilité, reconstruire toute la section de la page (par exemple avec innerHTML)

Après chaque modification, le navigateur recalcule les styles et la mise en page (layout) et repeint la partie modifiée de la page. Si tu reconstruis toute une section, il le fait pour tous ses éléments, même si un seul a changé.

C'est comme reconcevoir tout le vaisseau spatial alors que tu veux simplement changer la couleur d'un seul bouton du tableau de bord !

Comment fonctionne le Virtual DOM

React a introduit une approche révolutionnaire :

  1. L'utilisateur effectue une action
  2. React crée un nouveau Virtual DOM (une copie JavaScript légère du DOM réel)
  3. React compare le Virtual DOM précédent avec le nouveau (l'algorithme de « diffing »)
  4. React met à jour uniquement les éléments du DOM réel qui ont réellement changé

C'est comme disposer d'une maquette holographique du vaisseau spatial, où tu planifies les changements et les testes avant de les appliquer au vrai vaisseau. Ce processus est incroyablement efficace, surtout dans les applications complexes où de nombreux éléments peuvent changer simultanément.

Le Virtual DOM en action - exemple

Prenons un exemple simple : nous avons une liste de tâches, et un utilisateur vient de marquer une tâche comme terminée.

Dans l'approche traditionnelle :

  1. Soit tu trouves et modifies toi-même le bon élément de la liste - et tu dois penser à chaque endroit de ce type dans ton code
  2. Soit tu reconstruis toute la liste (par exemple avec innerHTML) - plus simple, mais le navigateur traite alors tous les éléments de la liste

Avec React et le Virtual DOM :

  1. React crée une nouvelle version du Virtual DOM avec l'état mis à jour de la tâche
  2. Il la compare avec la version précédente
  3. Il trouve un seul élément modifié (la tâche marquée comme terminée)
  4. Il met à jour uniquement cet élément précis dans le DOM réel

Détails d'implémentation du Virtual DOM

Le Virtual DOM fonctionne à plusieurs niveaux :

  1. Représentation des éléments - Chaque élément JSX est transformé en un objet JavaScript léger représentant un nœud du DOM
  2. Réconciliation - Le processus de comparaison de deux arbres Virtual DOM et de calcul de l'ensemble minimal d'opérations nécessaires pour mettre à jour le DOM réel
  3. Regroupement (Batching) - React regroupe plusieurs mises à jour en une seule session afin de minimiser le nombre d'opérations sur le DOM réel
1// Ce code JSX
2<div className="task-list">
3  <Task completed={true} name="Task 1" />
4  <Task completed={false} name="Task 2" />
5</div>
6
7// Est transformé en une structure semblable à celle-ci (simplifiée)
8{
9  type: 'div',
10  props: {
11    className: 'task-list',
12    children: [
13      {
14        type: Task,
15        props: { completed: true, name: 'Task 1' }
16      },
17      {
18        type: Task,
19        props: { completed: false, name: 'Task 2' }
20      }
21    ]
22  }
23}

Avantages du Virtual DOM

Les avantages de cette approche sont considérables :

  1. Performance - surtout dans les applications complexes où l'approche traditionnelle pourrait provoquer des ralentissements perceptibles
  2. Abstraction du DOM - les développeurs peuvent se concentrer sur la logique de l'application plutôt que sur les détails de manipulation du DOM
  3. Prévisibilité - il est plus facile de suivre les changements d'état et de déboguer les problèmes
  4. Facilité de test - les composants peuvent être testés de façon isolée
  5. Multiplateforme - puisque le Virtual DOM est un simple objet JavaScript, le même concept peut être utilisé sur différentes plateformes (Web, mobile avec React Native)

Fiber - la nouvelle génération du Virtual DOM

Depuis React 16, une nouvelle implémentation de l'algorithme de réconciliation appelée Fiber a été introduite. Cette architecture permet :

  • Des mises à jour interruptibles - les longues opérations de rendu peuvent être découpées en plus petites parties
  • La priorisation des mises à jour - certaines mises à jour (par exemple les animations) peuvent avoir une priorité plus élevée
  • Une meilleure gestion des erreurs
  • Une meilleure prise en charge du mode concurrent (Concurrent Mode)

Cette évolution du Virtual DOM montre comment React fait constamment évoluer ses technologies clés pour relever les défis des applications web modernes.

Code de cette leçon : App.jsx
1import React from 'react';
2
3// Composant démontrant le DOM traditionnel
4function TraditionalDOMDemo() {
5  const [updates, setUpdates] = React.useState(0);
6  const [isAnimating, setIsAnimating] = React.useState(false);
7
8  const simulateTraditionalUpdate = () => {
9    setIsAnimating(true);
10    setUpdates(prev => prev + 1);
11
12    setTimeout(() => {
13      setIsAnimating(false);
14    }, 1000);
15  };
16
17  return (
18    <div style={{
19      backgroundColor: '#ffebee',
20      padding: '20px',
21      borderRadius: '8px',
22      border: '2px solid #f44336',
23      margin: '15px 0'
24    }}>
25      <h3 style={{ color: '#c62828', marginBottom: '15px' }}>
26        DOM traditionnel (lent)
27      </h3>
28
29      <div style={{
30        display: 'flex',
31        alignItems: 'center',
32        marginBottom: '15px',
33        gap: '15px'
34      }}>
35        <button
36          onClick={simulateTraditionalUpdate}
37          disabled={isAnimating}
38          style={{
39            padding: '10px 20px',
40            backgroundColor: isAnimating ? '#ccc' : '#f44336',
41            color: 'white',
42            border: 'none',
43            borderRadius: '4px',
44            cursor: isAnimating ? 'not-allowed' : 'pointer'
45          }}
46        >
47          {isAnimating ? 'Mise à jour...' : 'Mettre à jour le DOM'}
48        </button>
49
50        <div style={{
51          padding: '8px 15px',
52          backgroundColor: 'white',
53          borderRadius: '4px',
54          border: '1px solid #ccc'
55        }}>
56          Mises à jour : {updates}
57        </div>
58      </div>
59
60      <div style={{
61        backgroundColor: 'white',
62        padding: '15px',
63        borderRadius: '4px',
64        opacity: isAnimating ? 0.5 : 1,
65        transition: 'opacity 1s'
66      }}>
67        <h4>Étapes de mise à jour traditionnelle :</h4>
68        <ol style={{ color: '#666' }}>
69          <li>Trouver tous les éléments à modifier</li>
70          <li>Modifier chaque élément individuellement</li>
71          <li>Recalculer les styles et la mise en page (reflow)</li>
72          <li>Repeindre la partie modifiée de la page (repaint)</li>
73        </ol>
74        {isAnimating && (
75          <div style={{
76            color: '#f44336',
77            fontStyle: 'italic',
78            marginTop: '10px'
79          }}>
80            ⏳ Mise à jour... Cela peut prendre un moment sur les grandes pages !
81          </div>
82        )}
83      </div>
84    </div>
85  );
86}
87
88// Composant démontrant le Virtual DOM
89function VirtualDOMDemo() {
90  const [updates, setUpdates] = React.useState(0);
91  const [isProcessing, setIsProcessing] = React.useState(false);
92  const [virtualChanges, setVirtualChanges] = React.useState([]);
93
94  const simulateVirtualDOMUpdate = () => {
95    setIsProcessing(true);
96    setVirtualChanges(['Création du Virtual DOM...']);
97
98    setTimeout(() => {
99      setVirtualChanges(['Création du Virtual DOM...', 'Comparaison avec la version précédente...']);
100    }, 300);
101
102    setTimeout(() => {
103      setVirtualChanges([
104        'Création du Virtual DOM...',
105        'Comparaison avec la version précédente...',
106        '1 changement trouvé à appliquer'
107      ]);
108    }, 600);
109
110    setTimeout(() => {
111      setVirtualChanges([
112        'Création du Virtual DOM...',
113        'Comparaison avec la version précédente...',
114        '1 changement trouvé à appliquer',
115        'Mise à jour des seuls éléments nécessaires'
116      ]);
117      setUpdates(prev => prev + 1);
118      setIsProcessing(false);
119    }, 900);
120
121    setTimeout(() => {
122      setVirtualChanges([]);
123    }, 2000);
124  };
125
126  return (
127    <div style={{
128      backgroundColor: '#e8f5e8',
129      padding: '20px',
130      borderRadius: '8px',
131      border: '2px solid #4caf50',
132      margin: '15px 0'
133    }}>
134      <h3 style={{ color: '#2e7d32', marginBottom: '15px' }}>
135        Virtual DOM de React (rapide)
136      </h3>
137
138      <div style={{
139        display: 'flex',
140        alignItems: 'center',
141        marginBottom: '15px',
142        gap: '15px'
143      }}>
144        <button
145          onClick={simulateVirtualDOMUpdate}
146          disabled={isProcessing}
147          style={{
148            padding: '10px 20px',
149            backgroundColor: isProcessing ? '#ccc' : '#4caf50',
150            color: 'white',
151            border: 'none',
152            borderRadius: '4px',
153            cursor: isProcessing ? 'not-allowed' : 'pointer'
154          }}
155        >
156          {isProcessing ? 'Traitement...' : 'Mettre à jour le Virtual DOM'}
157        </button>
158
159        <div style={{
160          padding: '8px 15px',
161          backgroundColor: 'white',
162          borderRadius: '4px',
163          border: '1px solid #ccc'
164        }}>
165          Mises à jour : {updates}
166        </div>
167      </div>
168
169      <div style={{
170        backgroundColor: 'white',
171        padding: '15px',
172        borderRadius: '4px'
173      }}>
174        <h4>Processus du Virtual DOM :</h4>
175        <ul style={{ color: '#666', minHeight: '100px' }}>
176          {virtualChanges.map((change, index) => (
177            <li key={index} style={{
178              marginBottom: '5px',
179              opacity: 0,
180              animation: 'fadeIn 0.5s forwards',
181              animationDelay: `${index * 0.2}s`
182            }}>
183              {change}
184            </li>
185          ))}
186          {!isProcessing && virtualChanges.length === 0 && (
187            <li style={{ color: '#999', fontStyle: 'italic' }}>
188              Clique sur le bouton pour voir le processus du Virtual DOM en action
189            </li>
190          )}
191        </ul>
192      </div>
193
194      <style>
195        {`
196          @keyframes fadeIn {
197            from { opacity: 0; }
198            to { opacity: 1; }
199          }
200        `}
201      </style>
202    </div>
203  );
204}
205
206// Composant démontrant les différences de performance
207function PerformanceDifference() {
208  const [itemCount, setItemCount] = React.useState(100);
209  const [items, setItems] = React.useState([]);
210
211  React.useEffect(() => {
212    const newItems = Array.from({ length: itemCount }, (_, i) => ({
213      id: i,
214      name: `Élément ${i + 1}`,
215      value: Math.floor(Math.random() * 100)
216    }));
217    setItems(newItems);
218  }, [itemCount]);
219
220  const updateAllItems = () => {
221    setItems(prevItems =>
222      prevItems.map(item => ({
223        ...item,
224        value: Math.floor(Math.random() * 100)
225      }))
226    );
227  };
228
229  return (
230    <div style={{
231      backgroundColor: '#f3e5f5',
232      padding: '20px',
233      borderRadius: '8px',
234      border: '2px solid #9c27b0',
235      margin: '15px 0'
236    }}>
237      <h3 style={{ color: '#7b1fa2', marginBottom: '15px' }}>
238        Démonstration des performances du Virtual DOM
239      </h3>
240
241      <div style={{
242        display: 'flex',
243        gap: '15px',
244        marginBottom: '15px',
245        flexWrap: 'wrap',
246        alignItems: 'center'
247      }}>
248        <label style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
249          Nombre d'éléments :
250          <input
251            type="range"
252            min="50"
253            max="1000"
254            step="50"
255            value={itemCount}
256            onChange={(e) => setItemCount(Number(e.target.value))}
257            style={{ width: '150px' }}
258          />
259          <span style={{ minWidth: '60px' }}>{itemCount}</span>
260        </label>
261
262        <button
263          onClick={updateAllItems}
264          style={{
265            padding: '10px 20px',
266            backgroundColor: '#9c27b0',
267            color: 'white',
268            border: 'none',
269            borderRadius: '4px',
270            cursor: 'pointer'
271          }}
272        >
273          Tout mettre à jour
274        </button>
275      </div>
276
277      <div style={{
278        display: 'grid',
279        gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))',
280        gap: '8px',
281        maxHeight: '200px',
282        overflow: 'auto',
283        backgroundColor: 'white',
284        padding: '15px',
285        borderRadius: '4px'
286      }}>
287        {items.map(item => (
288          <div
289            key={item.id}
290            style={{
291              padding: '8px',
292              backgroundColor: `hsl(${item.value * 3.6}, 70%, 90%)`,
293              borderRadius: '4px',
294              textAlign: 'center',
295              fontSize: '12px',
296              transition: 'background-color 0.3s'
297            }}
298          >
299            <div style={{ fontWeight: 'bold' }}>{item.name}</div>
300            <div>{item.value}</div>
301          </div>
302        ))}
303      </div>
304
305      <div style={{
306        marginTop: '15px',
307        padding: '10px',
308        backgroundColor: 'white',
309        borderRadius: '4px',
310        fontSize: '14px',
311        color: '#666'
312      }}>
313        <strong>Remarque :</strong> Même en mettant à jour {itemCount} éléments,
314        React, grâce au Virtual DOM, traite les changements en douceur et ne met à jour que
315        les éléments qui ont réellement changé !
316      </div>
317    </div>
318  );
319}
320
321// Composant avec visualisation interactive du processus
322function VirtualDOMVisualization() {
323  const [step, setStep] = React.useState(0);
324  const steps = [
325    { title: "État initial", desc: "Nous avons un Virtual DOM existant" },
326    { title: "Changement d'état", desc: "L'utilisateur effectue une action (par exemple, clique sur un bouton)" },
327    { title: "Nouveau Virtual DOM", desc: "React crée un nouveau Virtual DOM" },
328    { title: "Algorithme de diff", desc: "React compare l'ancien et le nouveau Virtual DOM" },
329    { title: "Changements minimaux", desc: "React identifie l'ensemble minimal de changements" },
330    { title: "Mise à jour du DOM", desc: "Seuls les éléments nécessaires sont mis à jour" }
331  ];
332
333  const nextStep = () => {
334    setStep(prev => (prev + 1) % steps.length);
335  };
336
337  const resetSteps = () => {
338    setStep(0);
339  };
340
341  return (
342    <div style={{
343      backgroundColor: '#fff3e0',
344      padding: '20px',
345      borderRadius: '8px',
346      border: '2px solid #ff9800',
347      margin: '15px 0'
348    }}>
349      <h3 style={{ color: '#f57c00', marginBottom: '15px' }}>
350        Pas à pas : comment fonctionne le Virtual DOM
351      </h3>
352
353      <div style={{
354        display: 'flex',
355        gap: '10px',
356        marginBottom: '20px'
357      }}>
358        <button onClick={nextStep} style={{
359          padding: '10px 20px',
360          backgroundColor: '#ff9800',
361          color: 'white',
362          border: 'none',
363          borderRadius: '4px',
364          cursor: 'pointer'
365        }}>
366          Étape suivante
367        </button>
368
369        <button onClick={resetSteps} style={{
370          padding: '10px 20px',
371          backgroundColor: '#666',
372          color: 'white',
373          border: 'none',
374          borderRadius: '4px',
375          cursor: 'pointer'
376        }}>
377          Reset
378        </button>
379      </div>
380
381      <div style={{
382        backgroundColor: 'white',
383        padding: '20px',
384        borderRadius: '8px',
385        minHeight: '120px'
386      }}>
387        <div style={{
388          display: 'flex',
389          alignItems: 'center',
390          marginBottom: '15px'
391        }}>
392          <div style={{
393            width: '40px',
394            height: '40px',
395            backgroundColor: '#ff9800',
396            color: 'white',
397            borderRadius: '50%',
398            display: 'flex',
399            alignItems: 'center',
400            justifyContent: 'center',
401            fontWeight: 'bold',
402            marginRight: '15px'
403          }}>
404            {step + 1}
405          </div>
406          <h4 style={{ margin: 0, color: '#333' }}>
407            {steps[step].title}
408          </h4>
409        </div>
410
411        <p style={{ color: '#666', fontSize: '16px', lineHeight: '1.5' }}>
412          {steps[step].desc}
413        </p>
414
415        <div style={{
416          marginTop: '15px',
417          display: 'flex',
418          gap: '5px'
419        }}>
420          {steps.map((_, index) => (
421            <div
422              key={index}
423              style={{
424                width: '12px',
425                height: '12px',
426                borderRadius: '50%',
427                backgroundColor: index <= step ? '#ff9800' : '#ddd',
428                transition: 'background-color 0.3s'
429              }}
430            />
431          ))}
432        </div>
433      </div>
434    </div>
435  );
436}
437
438export default function App() {
439  return (
440    <div style={{
441      fontFamily: 'Arial, sans-serif',
442      maxWidth: '900px',
443      margin: '0 auto',
444      padding: '20px'
445    }}>
446      <header style={{
447        textAlign: 'center',
448        marginBottom: '30px',
449        padding: '25px',
450        background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
451        color: 'white',
452        borderRadius: '12px'
453      }}>
454        <h1 style={{ margin: '0 0 10px 0', fontSize: '2.2rem' }}>
455          Le Virtual DOM et ses avantages
456        </h1>
457        <p style={{ margin: 0, fontSize: '1rem', opacity: 0.9 }}>
458          Découvre le secret de la performance de React et comprends pourquoi le Virtual DOM
459          est une véritable révolution dans le monde du front-end
460        </p>
461      </header>
462
463      <section style={{ marginBottom: '30px' }}>
464        <h2 style={{
465          color: '#333',
466          borderBottom: '2px solid #667eea',
467          paddingBottom: '10px',
468          marginBottom: '20px'
469        }}>
470          DOM traditionnel vs Virtual DOM
471        </h2>
472
473        <TraditionalDOMDemo />
474        <VirtualDOMDemo />
475      </section>
476
477      <VirtualDOMVisualization />
478      <PerformanceDifference />
479
480      <div style={{
481        backgroundColor: '#e3f2fd',
482        padding: '25px',
483        borderRadius: '12px',
484        marginTop: '30px'
485      }}>
486        <h3 style={{ color: '#1976d2', textAlign: 'center', marginBottom: '20px' }}>
487          Avantages clés du Virtual DOM
488        </h3>
489
490        <div style={{
491          display: 'grid',
492          gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
493          gap: '20px'
494        }}>
495          <div style={{ textAlign: 'center' }}>
496            <div style={{ fontSize: '40px', marginBottom: '10px' }}>↯</div>
497            <h4 style={{ color: '#1976d2' }}>Performance</h4>
498            <p style={{ margin: 0, color: '#666' }}>
499              Mises à jour minimales du DOM = applications plus rapides
500            </p>
501          </div>
502
503          <div style={{ textAlign: 'center' }}>
504            <div style={{ fontSize: '40px', marginBottom: '10px' }}>◆</div>
505            <h4 style={{ color: '#1976d2' }}>Prévisibilité</h4>
506            <p style={{ margin: 0, color: '#666' }}>
507              Suivi des changements et débogage plus faciles
508            </p>
509          </div>
510
511          <div style={{ textAlign: 'center' }}>
512            <div style={{ fontSize: '40px', marginBottom: '10px' }}>◆</div>
513            <h4 style={{ color: '#1976d2' }}>Testabilité</h4>
514            <p style={{ margin: 0, color: '#666' }}>
515              Les composants peuvent être testés de façon isolée
516            </p>
517          </div>
518
519          <div style={{ textAlign: 'center' }}>
520            <div style={{ fontSize: '40px', marginBottom: '10px' }}>●</div>
521            <h4 style={{ color: '#1976d2' }}>Multiplateforme</h4>
522            <p style={{ margin: 0, color: '#666' }}>
523              Le même concept fonctionne partout
524            </p>
525          </div>
526        </div>
527      </div>
528    </div>
529  );
530}

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. Qu'est-ce que le Virtual DOM dans React ?

  2. 2. Que sont les composants dans React ?

  3. 3. Quel est le principal avantage du Virtual DOM dans React ?

  4. 4. Que fait l'algorithme de diffing (réconciliation) dans React ?

Tâches pratiques dans le jeu

  • Clique dans l'ordre

    Ordonne le processus du Virtual DOM dans l'ordre :

Articles utiles