Kurs JavaScript i React · Moduł 1: Podstawy React
Virtual DOM i jego zalety
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:
- Znaleźć w DOM wszystkie elementy, których dotyczy zmiana
- 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:
- Użytkownik wykonuje akcję
- React tworzy nowy Wirtualny DOM (lekką, javascriptową kopię rzeczywistego DOM)
- React porównuje poprzedni Wirtualny DOM z nowym (algorytm "diffing")
- 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:
- Albo sam znajdujesz i zmieniasz właściwy element listy - i musisz pamiętać o każdym takim miejscu w kodzie
- Albo budujesz całą listę od nowa (np. przez innerHTML) - prościej, ale przeglądarka przetwarza wtedy wszystkie elementy listy
Z React i Virtual DOM:
- React tworzy nową wersję Virtual DOM z zaktualizowanym stanem zadania
- Porównuje ją z poprzednią wersją
- Odnajduje tylko jeden zmieniony element (zadanie oznaczone jako ukończone)
- Aktualizuje tylko ten konkretny element w rzeczywistym DOM
Szczegóły implementacji Virtual DOM
Virtual DOM działa na kilku poziomach:
- Reprezentacja elementów - Każdy element JSX jest przekształcany w lekki obiekt JavaScript reprezentujący węzeł DOM
- Reconciliation - Proces porównywania dwóch drzew Virtual DOM i obliczania minimalnego zestawu operacji potrzebnych do aktualizacji rzeczywistego DOM
- 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:
- Wydajność - szczególnie w złożonych aplikacjach, gdzie tradycyjne podejście mogłoby powodować zauważalne opóźnienia
- Abstrakcja DOM - deweloperzy mogą skupić się na logice aplikacji, a nie na szczegółach manipulacji DOM
- Przewidywalność - łatwiej śledzić zmiany stanu i debugować problemy
- Łatwość testowania - komponenty można testować w izolacji
- 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. Co to jest Virtual DOM w React?
2. Co to są komponenty w React?
3. Jaka jest główna zaleta Virtual DOM w React?
4. Na czym polega algorytm diffing (reconciliation) w React?
Zadania praktyczne w grze
- Klikanie w kolejności
Ułóż proces działania Virtual DOM: