Kurs JavaScript i React · Moduł 1: Podstawy React

Virtual DOM i jego zalety

4 min czytania
W tej lekcji6

Jedną z najbardziej innowacyjnych cech React jest koncepcja Wirtualnego DOM (Document Object Model). Aby zrozumieć jego znaczenie, musimy najpierw zrozumieć problem, który rozwiązuje.

Problem z tradycyjnym DOM

Wyobraź sobie, że DOM przeglądarki to ogromna mapa gwiezdna, na której każda gwiazda, planeta i stacja kosmiczna musi być precyzyjnie umieszczona. Zmiana na tej mapie ma swój koszt: przeglądarka musi przeliczyć położenie obiektów, na które ta zmiana wpływa.

W tradycyjnym podejściu, gdy użytkownik wykonuje akcję (np. klika przycisk), musisz sam:

  1. Znaleźć w DOM wszystkie elementy, których dotyczy zmiana
  2. Zmienić każdy z nich ręcznie - albo, dla wygody, przebudować cały fragment strony (np. przez innerHTML)

Po każdej zmianie przeglądarka przelicza style i układ (layout), a potem przerysowuje (paint) zmienioną część strony. Przy przebudowie całego fragmentu robi to dla wszystkich jego elementów, choć zmienił się tylko jeden.

To jak przeprojektowywanie całego statku kosmicznego, gdy chcesz tylko zmienić kolor jednego przycisku na panelu sterowania!

Jak działa Virtual DOM

React wprowadził rewolucyjne podejście:

  1. Użytkownik wykonuje akcję
  2. React tworzy nowy Wirtualny DOM (lekką, javascriptową kopię rzeczywistego DOM)
  3. React porównuje poprzedni Wirtualny DOM z nowym (algorytm "diffing")
  4. React aktualizuje tylko te elementy rzeczywistego DOM, które faktycznie się zmieniły

To jak posiadanie holograficznej makiety statku kosmicznego, na której planujesz zmiany i testujesz je, zanim wprowadzisz je do prawdziwego statku. Ten proces jest niewiarygodnie efektywny, szczególnie w złożonych aplikacjach, gdzie wiele elementów może zmieniać się jednocześnie.

Przykład działania Virtual DOM

Rozważmy prosty przykład: mamy listę zadań do wykonania, a użytkownik właśnie oznaczył jedno zadanie jako ukończone.

W tradycyjnym podejściu:

  1. Albo sam znajdujesz i zmieniasz właściwy element listy - i musisz pamiętać o każdym takim miejscu w kodzie
  2. Albo budujesz całą listę od nowa (np. przez innerHTML) - prościej, ale przeglądarka przetwarza wtedy wszystkie elementy listy

Z React i Virtual DOM:

  1. React tworzy nową wersję Virtual DOM z zaktualizowanym stanem zadania
  2. Porównuje ją z poprzednią wersją
  3. Odnajduje tylko jeden zmieniony element (zadanie oznaczone jako ukończone)
  4. Aktualizuje tylko ten konkretny element w rzeczywistym DOM

Szczegóły implementacji Virtual DOM

Virtual DOM działa na kilku poziomach:

  1. Reprezentacja elementów - Każdy element JSX jest przekształcany w lekki obiekt JavaScript reprezentujący węzeł DOM
  2. Reconciliation - Proces porównywania dwóch drzew Virtual DOM i obliczania minimalnego zestawu operacji potrzebnych do aktualizacji rzeczywistego DOM
  3. Batching - React grupuje wiele aktualizacji w jedną sesję, aby zminimalizować liczbę operacji na rzeczywistym DOM
1// Ten kod JSX
2<div className="task-list">
3  <Task completed={true} name="Zadanie 1" />
4  <Task completed={false} name="Zadanie 2" />
5</div>
6
7// Jest przekształcany w strukturę podobną do tej (uproszczone)
8{
9  type: 'div',
10  props: {
11    className: 'task-list',
12    children: [
13      {
14        type: Task,
15        props: { completed: true, name: 'Zadanie 1' }
16      },
17      {
18        type: Task,
19        props: { completed: false, name: 'Zadanie 2' }
20      }
21    ]
22  }
23}

Zalety Virtual DOM

Zalety tego podejścia są ogromne:

  1. Wydajność - szczególnie w złożonych aplikacjach, gdzie tradycyjne podejście mogłoby powodować zauważalne opóźnienia
  2. Abstrakcja DOM - deweloperzy mogą skupić się na logice aplikacji, a nie na szczegółach manipulacji DOM
  3. Przewidywalność - łatwiej śledzić zmiany stanu i debugować problemy
  4. Łatwość testowania - komponenty można testować w izolacji
  5. Cross-platform - ponieważ Virtual DOM to zwykły obiekt JavaScript, ta sama koncepcja może być używana na różnych platformach (Web, Mobile z React Native)

Fiber - nowa generacja Virtual DOM

Od wersji React 16, wprowadzono nową implementację algorytmu reconciliation o nazwie Fiber. Ta architektura umożliwia:

  • Przerywalne aktualizacje - długie operacje renderowania mogą zostać podzielone na mniejsze części
  • Priorytetyzację aktualizacji - niektóre aktualizacje (np. animacje) mogą mieć większy priorytet
  • Lepszą obsługę błędów
  • Lepsze wsparcie dla Concurrent Mode

Ta ewolucja Virtual DOM pokazuje, jak React nieustannie rozwija swoje kluczowe technologie, aby sprostać wyzwaniom nowoczesnych aplikacji internetowych.

Kod do tej lekcji: App.jsx
1import React from 'react';
2
3// Komponent demonstrujący tradycyjny DOM
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        Tradycyjny DOM (Powolny)
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 ? 'Aktualizuję...' : 'Aktualizuj DOM'}
48        </button>
49
50        <div style={{
51          padding: '8px 15px',
52          backgroundColor: 'white',
53          borderRadius: '4px',
54          border: '1px solid #ccc'
55        }}>
56          Aktualizacji: {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>Kroki tradycyjnej aktualizacji:</h4>
68        <ol style={{ color: '#666' }}>
69          <li>Znajdź wszystkie elementy do zmiany</li>
70          <li>Zmień każdy element osobno</li>
71          <li>Przelicz style i układ (reflow)</li>
72          <li>Przerysuj zmienioną część strony (repaint)</li>
73        </ol>
74        {isAnimating && (
75          <div style={{
76            color: '#f44336',
77            fontStyle: 'italic',
78            marginTop: '10px'
79          }}>
80            ⏳ Aktualizowanie... To może zająć chwilę dla dużych stron!
81          </div>
82        )}
83      </div>
84    </div>
85  );
86}
87
88// Komponent demonstrujący 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(['Tworzę Virtual DOM...']);
97
98    setTimeout(() => {
99      setVirtualChanges(['Tworzę Virtual DOM...', 'Porównuję z poprzednią wersją...']);
100    }, 300);
101
102    setTimeout(() => {
103      setVirtualChanges([
104        'Tworzę Virtual DOM...',
105        'Porównuję z poprzednią wersją...',
106        'Znaleziono 1 zmianę do zaktualizowania'
107      ]);
108    }, 600);
109
110    setTimeout(() => {
111      setVirtualChanges([
112        'Tworzę Virtual DOM...',
113        'Porównuję z poprzednią wersją...',
114        'Znaleziono 1 zmianę do zaktualizowania',
115        'Aktualizuję tylko potrzebne elementy'
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 React (Szybki)
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 ? 'Przetwarzam...' : 'Aktualizuj 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          Aktualizacji: {updates}
166        </div>
167      </div>
168
169      <div style={{
170        backgroundColor: 'white',
171        padding: '15px',
172        borderRadius: '4px'
173      }}>
174        <h4>Proces 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              Kliknij przycisk, aby zobaczyć proces Virtual DOM w akcji
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// Komponent demonstrujący różnice w wydajności
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: `Element ${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        Demonstracja Wydajności 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          Liczba elementów:
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          Aktualizuj Wszystkie
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>Zauważ:</strong> Nawet przy aktualizacji {itemCount} elementów,
314        React dzięki Virtual DOM płynnie przetwarza zmiany i aktualizuje tylko
315        te elementy, które rzeczywiście się zmieniły!
316      </div>
317    </div>
318  );
319}
320
321// Komponent z interaktywną wizualizacją procesu
322function VirtualDOMVisualization() {
323  const [step, setStep] = React.useState(0);
324  const steps = [
325    { title: "Stan Początkowy", desc: "Mamy istniejący Virtual DOM" },
326    { title: "Zmiana Stanu", desc: "Użytkownik wykonuje akcję (np. klika przycisk)" },
327    { title: "Nowy Virtual DOM", desc: "React tworzy nowy Virtual DOM" },
328    { title: "Diff Algorithm", desc: "React porównuje stary i nowy Virtual DOM" },
329    { title: "Minimalne Zmiany", desc: "React identyfikuje minimalny zestaw zmian" },
330    { title: "Aktualizacja DOM", desc: "Tylko potrzebne elementy są aktualizowane" }
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        Krok Po Kroku: Jak Działa 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          Następny Krok
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          Virtual DOM i jego Zalety
456        </h1>
457        <p style={{ margin: 0, fontSize: '1rem', opacity: 0.9 }}>
458          Odkryj sekret wydajności React i zrozum, dlaczego Virtual DOM
459          to prawdziwa rewolucja w świecie front-endu
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          Tradycyjny DOM 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          Kluczowe Zalety 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' }}>Wydajność</h4>
498            <p style={{ margin: 0, color: '#666' }}>
499              Minimalne aktualizacje DOM = szybsze aplikacje
500            </p>
501          </div>
502
503          <div style={{ textAlign: 'center' }}>
504            <div style={{ fontSize: '40px', marginBottom: '10px' }}>◆</div>
505            <h4 style={{ color: '#1976d2' }}>Przewidywalność</h4>
506            <p style={{ margin: 0, color: '#666' }}>
507              Łatwiejsze śledzenie zmian i debugowanie
508            </p>
509          </div>
510
511          <div style={{ textAlign: 'center' }}>
512            <div style={{ fontSize: '40px', marginBottom: '10px' }}>◆</div>
513            <h4 style={{ color: '#1976d2' }}>Testowalność</h4>
514            <p style={{ margin: 0, color: '#666' }}>
515              Komponenty można testować w izolacji
516            </p>
517          </div>
518
519          <div style={{ textAlign: 'center' }}>
520            <div style={{ fontSize: '40px', marginBottom: '10px' }}>●</div>
521            <h4 style={{ color: '#1976d2' }}>Cross-platform</h4>
522            <p style={{ margin: 0, color: '#666' }}>
523              Ta sama koncepcja działa wszędzie
524            </p>
525          </div>
526        </div>
527      </div>
528    </div>
529  );
530}

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Co to jest Virtual DOM w React?

  2. 2. Co to są komponenty w React?

  3. 3. Jaka jest główna zaleta Virtual DOM w React?

  4. 4. Na czym polega algorytm diffing (reconciliation) w React?

Zadania praktyczne w grze

  • Klikanie w kolejności

    Ułóż proces działania Virtual DOM:

Przydatne artykuły