Rozszyfrowanie różnic w Virtual DOM: co porównuje React i dlaczego to ma znaczenie
Zrozum, czym naprawdę jest wirtualny DOM w React, jak proces synchronizacji porównuje dwa drzewa elementów, jakie zmiany zachodzą w fazie zapisu oraz skąd pochodzi korzyść pod względem wydajności.
Interfejsy zmieniają się ciągle: pojawia się powiadomienie, przycisk się włącza/wyłącza, licznik się zwiększa, lista jest ponownie renderowana. Każda taka zmiana musi dotrzeć do DOM-a przeglądarki, a nieostrożna aktualizacja DOM-a to skuteczny sposób na spowolnienie strony. Rozwiązaniem React jest wirtualny DOM w połączeniu z krokiem porównawczym zwanym rekonsolidacją. Ten artykuł wyjaśnia, co React przechowuje w pamięci, jak określa, co się zmieniło, oraz dlaczego to pomaga, aby można było racjonalnie analizować aktualizacje przy użyciu dokładnego modelu mentalnego.
Dlaczego bezpośrednie zapisywanie do DOM-a jest kosztowne
Przed React aktualizacje interfejsu były imperatywne: znaleźć węzeł i go zmienić.
document.getElementById("title").innerText = "Hello";
To jest w porządku na małej stronie. Gdy setki lub tysiące elementów często się zmieniają, staje się to zarówno powolne, jak i trudne do zarządzania, ponieważ zmiana w DOM-ie może zmusić przeglądarkę do ponownego wykonania:
- obliczeń stylów i układu
- przepłynięcia otaczającej treści
Za każdym razem przeglądarka ponownie oblicza pozycję elementów, ich rozmiar oraz które piksele należy przerysować. Ta operacja jest znacznie bardziej kosztowna niż tworzenie lub porównywanie obiektów JavaScript. Ponadto wiele fragmentów kodu, które niezależnie wpływają na DOM, ułatwiają wycofanie się ekranu ze synchronizacji z stanem aplikacji.
Czym właściwie jest Virtual DOM
Prawdziwy DOM
Prawdziwy DOM to żywy drzewo węzłów, które przeglądarka buduje na podstawie kodu markup – jeden węzeł na element. Nawet to:
<body>
<h1>Hello</h1>
</body>
staje się wewnętrznym drzewem powiązanym z układem i rysowaniem, a częste modyfikowanie go jest tym, co uruchamia powyższe kosztowne operacje.
Wirtualna reprezentacja
Wirtualny DOM jest często określany jako lżejsza kopia rzeczywistego DOM-u. Dokładniej mówiąc, jest to opis tego, jak powinien wyglądać interfejs użytkownika: zwykłe obiekty, które zwracają komponenty podczas renderowania. React najpierw aktualizuje ten opis, a dopiero potem wpływa na przeglądarkę. Jeden element wygląda mniej więcej tak:
{
type: "h1",
props: {
children: "Hello"
}
}
Takie obiekty są tanie w tworzeniu i porównywaniu, dzięki czemu React może ustalić, co się zmieniło, zanim przeprowadzi jakąkolwiek operację na rzeczywistym DOM-ie. JSX to po prostu składnia służąca do ich tworzenia.
Podczas pierwszego renderowania takiego komponentu:
function App() {
return <h1>Hello World</h1>;
}
React buduje wirtualne drzewo, tworzy odpowiadające mu węzły DOM-u i wstawia je na stronę. Obie struktury są teraz zgodne, a interfejs użytkownika jest widoczny.
Później obsługa kliknięcia może zaktualizować stan:
setCount(count + 1);
lub gdy rodzic przekazuje nowe właściwości. Sygnalizuje to, że część interfejsu może wymagać zmiany. React nie aktualizuje natychmiast DOM; planuje nowe renderowanie, a aktualizacje z tego samego wydarzenia są zazwyczaj grupowane razem.
Podczas tego renderowania React ponownie wywołuje komponent i otrzymuje nowy „drzewo”, więc teraz zawiera zarówno poprzednią, jak i nową wersję. Stare drzewo:
<h1>Hello</h1>
Nowe drzewo:
<h1>Hello Omer</h1>
Zadaniem jest dokładne określenie tego, co się różni.
Rozróżnianie różnic: jak działa proces porównywania
Rozróżnianie różnic oznacza porównywanie poprzedniego wirtualnego drzewa z nowym; samo to porównanie jest zazwyczaj nazywane procesem diffing. Dla każdej pozycji React sprawdza, czy element się zmienił, pozostał taki sam, czy został dodany lub usunięty. Koncepcyjnie:
Old Tree VS New Tree
Celem jest jak najmniejszy zestaw zmian, które przekształcą stary interfejs użytkownika w nowy. Pełny porównanie drzewa byłoby zbyt wolne, dlatego React wykorzystuje dwie heurystyki. Jeśli zmienia się typ elementu, na przykład z div na span, React zastępuje całe to poddrzewo, zamiast porównywać jego dzieci. W listach porównuje dzieci za pomocą key, dlatego klucze stabilne są ważne – informują one React, że element został przeniesiony, a nie zastąpiony.
Gdy zmienia się tylko tekst
Wcześniej:
<h1>Hello</h1>
Później:
<h1>Hello Omer</h1>
<h1> ma ten sam typ w tym samym miejscu; różni się tylko jego tekst. Dlatego React zachowuje istniejący węzeł DOM i aktualizuje tylko węzeł tekstowy, zamiast tworzyć element od nowa. Pomijanie wszystkich operacji, które nie są bezwzględnie konieczne, stanowi istotę optymalizacji.
Faza komitowania: zastosowanie tylko tych zmian, które wystąpiły
Po porównaniu różnic React zapisuje uzyskane zmiany do rzeczywistego DOM-u:
- aktualizacja tekstu
- ustawianie lub usuwanie atrybutów
- wstawianie nowych elementów
- usuwanie elementów, które zniknęły
To jest faza komitowania – jedyne miejsce, w którym React wpływa na DOM – i zmienia tylko to, co zostało zaznaczone w porównaniu różnic. Następnie przeglądarka ponownie rysuje afekowane obszary. Aby dowiedzieć się więcej na temat procesu przekształcania komitów w piksele, zapoznaj się z naszym przewodnikiem po renderowaniu w React od aktualizacji stanu do pikseli na ekranie.
Z czego wynika korzyść pod względem wydajności
Zysk wynika z mniejszej liczby kosztownych operacji DOM. Zamiast odbudowywać wszystko od nowa, React porównuje stare i nowe opisy, oblicza minimalne różnice i modyfikuje tylko dotknięte węzły. Ponieważ porównywanie obiektów jest znacznie tańsze niż modyfikacja DOM, przynosi to korzyści w dużych aplikacjach.
Dwie uwagi zapewniają uczciwość tego modelu. Virtual DOM nie jest z natury szybszy od starannie dostrojonego kodu DOM; jego wartość polega na umożliwieniu pisania deklaratywnego interfejsu użytkownika przy jednoczesnym uzyskiwaniu efektywnych aktualizacji. A renderowanie nie jest bezkosztowe: React nadal wywołuje twoje komponenty i porównuje ich wyniki, więc unikanie niepotrzebnego ponownego renderowania nadal ma znaczenie.
Główne wnioski
- Virtual DOM to zbiór zwykłych obiektów opisujących zamierzony interfejs użytkownika, który jest odbudowywany przy każdym renderowaniu.
key jako skrótów.Literatura pokrewna
- Poza rozmiarem bundle: odkrywanie tego, co naprawdę spowalnia twoją aplikację webową — Dlaczego redukcja kilobajtów rzadko naprawia wolną aplikację oraz jak śledzić rzeczywisty czas oczekiwania na serwerach, w procesie hydratacji, skryptach i obrazach od dostawców zewnętrznych.
- Gdzie napisana jest funkcja określa, co ona widzi: Scop leksykalny w JavaScript — Zrozum, jak JavaScript rozwiązuje nazwę zmiennej poprzez środowiska leksykalne, dlaczego miejsce wywołania nigdy nie ma znaczenia przy wyszukiwaniu oraz jak to wpływa na obsługiwanie zdarzeń w React.