Przejdź do treści głównej

Renderowanie, Zatwierdzanie i Montowanie

Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

ostrzeżenie

Ten dokument odnosi się do Nowej Architektury, która jest obecnie w trakcie wdrażania.

Renderer React Native przechodzi sekwencję operacji, aby przekształcić logikę Reacta w interfejs na platformie hosta. Ta sekwencja nazywana jest potokiem renderowania i występuje zarówno przy inicjalnym renderowaniu, jak i przy aktualizacjach stanu UI. W tym dokumencie omówimy potok renderowania oraz jego różnice w tych scenariuszach.

Potok renderowania można podzielić na trzy główne fazy:

  1. Renderowanie: React wykonuje logikę produktową tworząc Drzewo Elementów Reacta w JavaScript. Z tego drzewa renderer tworzy Cieniowane Drzewo Reacta w C++.

  2. Zatwierdzanie: Po pełnym utworzeniu Cieniowanego Drzewa Reacta renderer inicjuje zatwierdzenie. Promuje ono zarówno Drzewo Elementów Reacta, jak i nowe Cieniowane Drzewo Reacta jako "następne drzewo" do zamontowania. Planuje również obliczenie informacji o układzie.

  3. Montowanie: Cieniowane Drzewo Reacta, zawierające wyniki obliczeń układu, jest przekształcane w Drzewo Widoków Hosta.

Fazy potoku renderowania mogą występować na różnych wątkach. Więcej szczegółów znajdziesz w dokumencie Model Wątkowości.

Przepływ danych renderera React Native


Renderowanie początkowe

Wyobraź sobie, że chcesz wyrenderować następujący element:

jsx
function MyComponent() {
return (
<View>
<Text>Hello, World</Text>
</View>
);
}

// <MyComponent />

W powyższym przykładzie <MyComponent /> jest Elementem Reacta. React rekursywnie redukuje ten Element Reacta do terminalnego Komponentu Hostowego Reacta poprzez wywoływanie go (lub metody render w przypadku klas JavaScript), aż każdy Element Reacta nie może być dalej redukowany. W rezultacie powstaje Drzewo Elementów Reacta złożone z Komponentów Hostowych Reacta.

Faza 1. Renderowanie

Phase one: render

Podczas redukcji elementów, gdy każdy Element Reacta jest wywoływany, renderer synchronicznie tworzy Węzeł Cieniowy Reacta. Dzieje się to tylko dla Komponentów Hostowych Reacta, nie dla Komponentów Złożonych Reacta. W przykładzie <View> skutkuje utworzeniem obiektu ViewShadowNode, a <Text> - obiektu TextShadowNode. Istotne: nigdy nie powstaje Węzeł Cieniowy Reacta bezpośrednio reprezentujący <MyComponent>.

Gdy React tworzy relację rodzic-dziecko między dwoma Węzłami Elementów Reacta, renderer tworzy tę samą relację między odpowiadającymi Węzłami Cieniowymi Reacta. W ten sposób budowane jest Cieniowane Drzewo Reacta.

Dodatkowe szczegóły

  • Operacje (tworzenie Węzła Cieniowego Reacta, tworzenie relacji między węzłami) są synchroniczne i bezpieczne wątkowo, wykonywane z Reacta (JavaScript) do renderera (C++), zwykle na wątku JavaScript.

  • Drzewo Elementów Reacta (i jego składowe Węzły Elementów Reacta) nie istnieje wiecznie. Jest to tymczasowa reprezentacja materializowana przez "fibers" w React. Każdy "fiber" reprezentujący komponent hosta przechowuje wskaźnik C++ do Węzła Cieniowego Reacta, co umożliwia JSI. Więcej o "fibers" w tym dokumencie.

  • Drzewo Cieni React jest niemutowalne. Aby zaktualizować dowolny Węzeł Cieni React, renderer tworzy nowe Drzewo Cieni React. Jednak renderer udostępnia operacje klonowania, aby aktualizacje stanu były bardziej wydajne (szczegóły w sekcji Aktualizacje stanu React).

W powyższym przykładzie wynik fazy renderowania wygląda następująco:

Krok pierwszy

Po utworzeniu Drzewa Cieni React, renderer inicjuje zatwierdzenie (commit) Drzewa Elementów React.

Faza 2. Zatwierdzenie (Commit)

Faza druga: zatwierdzenie

Faza zatwierdzenia składa się z dwóch operacji: Obliczenia układu i Promocji drzewa.

  • Obliczenia układu: Ta operacja wylicza pozycję i rozmiar każdego Węzła Cieni React. W React Native wymaga to wywołania Yoga do obliczenia układu każdego węzła. Obliczenia wymagają stylów każdego Węzła Cieni React (pochodzących z Elementu React w JavaScript) oraz ograniczeń układu korzenia Drzewa Cieni React, które określają dostępną przestrzeń.

Krok drugi

  • Promocja drzewa (Nowe drzewo → Następne drzewo): Operacja promuje nowe Drzewo Cieni React jako "następne drzewo" do zamontowania. Oznacza to, że drzewo zawiera wszystkie informacje potrzebne do montażu i reprezentuje najnowszy stan Drzewa Elementów React. "Następne drzewo" montuje się przy kolejnym "tyku" wątku interfejsu.

Dodatkowe szczegóły

  • Operacje wykonywane są asynchronicznie na wątku w tle.

  • Większość obliczeń układu odbywa się w C++. Jednak układ niektórych komponentów (np. Text, TextInput) zależy od platformy docelowej. Yoga wywołuje wtedy funkcję zdefiniowaną w warstwie platformy docelowej.

Faza 3. Montaż (Mount)

Faza trzecia: montaż

W fazie montażu Drzewo Cieni React (z danymi z obliczeń układu) przekształca się w Drzewo Widżetów Hosta z renderowanymi pikselami na ekranie. Drzewo Elementów React prezentuje się następująco:

jsx
<View>
<Text>Hello, World</Text>
</View>

Renderer tworzy odpowiadający Widżet Host dla każdego Węzła Cieni React i montuje go na ekranie. Dla <View> tworzy instancję android.view.ViewGroup, dla <Text> - android.widget.TextView z tekstem "Hello World". Na iOS tworzy UIView z tekstem via NSLayoutManager. Każdy widżet konfigurowany jest z użyciem właściwości z odpowiadającego węzła cieni i danych układu.

Krok drugi

Montaż składa się z trzech kroków:

  • Różnicowanie drzew: Krok oblicza różnice między "poprzednio renderowanym drzewem" a "następnym drzewem" w C++. Wynik to lista operacji mutacji na widżetach (np. createView, updateView, removeView, deleteView, etc). Ten krok obejmuje również spłaszczenie drzewa cieni Reacta, aby uniknąć tworzenia niepotrzebnych widżetów. Szczegóły dotyczące tego algorytmu znajdują się w Spłaszczanie widoków.

  • Awansowanie drzewa (Następne drzewo → Renderowane drzewo): Atomowe zastąpienie "poprzednio renderowanego drzewa" przez "następne drzewo", aby faza montowania mogła obliczyć różnicę względem właściwego drzewa.

  • Montaż widoków: Ten etap stosuje atomowe operacje mutacji na odpowiadających im widokach hosta. Wykonywany jest na platformie hosta w wątku UI.

Dodatkowe szczegóły

  • Operacje są wykonywane synchronicznie w wątku UI. Jeśli faza commitu działa w wątku w tle, montaż jest planowany na następny „tick” wątku UI. Gdy commit odbywa się w wątku UI, montaż wykonywany jest synchronicznie w tym samym wątku.

  • Planowanie, implementacja i wykonanie fazy montażu silnie zależą od platformy hosta. Np. architektura renderera warstwy montażowej różni się obecnie między Androidem i iOS.

  • Podczas początkowego renderowania „poprzednio wyrenderowane drzewo” jest puste. Dlatego obliczanie różnic drzewa da listę operacji mutacji składającą się tylko z tworzenia widoków, ustawiania właściwości i dodawania widoków do siebie. Obliczanie różnic drzewa zyskuje na znaczeniu dla wydajności podczas przetwarzania Aktualizacji stanu Reacta.

  • W obecnych testach produkcyjnych React Shadow Tree zwykle składa się z ~600-1000 React Shadow Nodes (przed spłaszczeniem widoków), po spłaszczeniu drzewa redukują się do ~200 węzłów. W aplikacjach iPad lub desktopowych ilość ta może wzrosnąć 10-krotnie.


Aktualizacje stanu Reacta

Przeanalizujmy każdą fazę potoku renderowania podczas aktualizacji stanu React Element Tree. Załóżmy, że w początkowym renderze wyświetliłeś następujący komponent:

jsx
function MyComponent() {
return (
<View>
<View
style={{backgroundColor: 'red', height: 20, width: 20}}
/>
<View
style={{backgroundColor: 'blue', height: 20, width: 20}}
/>
</View>
);
}

Stosując zasady z sekcji Początkowe renderowanie, oczekujemy utworzenia następujących drzew:

Render pipeline 4

Zauważ, że Węzeł 3 mapuje na widok hosta z czerwonym tłem, a Węzeł 4 na widok z niebieskim tłem. Załóżmy, że w wyniku aktualizacji stanu w logice produktu JavaScript, tło pierwszego zagnieżdżonego <View> zmienia się z 'red' na 'yellow'. Oto jak może wyglądać nowe React Element Tree:

jsx
<View>
<View
style={{backgroundColor: 'yellow', height: 20, width: 20}}
/>
<View
style={{backgroundColor: 'blue', height: 20, width: 20}}
/>
</View>

Jak React Native przetwarza tę aktualizację?

Gdy następuje aktualizacja stanu, renderer musi koncepcyjnie zaktualizować React Element Tree, aby zmodyfikować już zamontowane widoki hosta. Ale aby zachować bezpieczeństwo wątków, zarówno React Element Tree, jak i React Shadow Tree muszą być niezmienne. Oznacza to, że zamiast mutować obecne drzewa, React musi utworzyć nową kopię każdego drzewa, uwzględniającą nowe właściwości, style i elementy potomne.

Przeanalizujmy każdą fazę potoku renderowania podczas aktualizacji stanu.

Faza 1. Renderowanie

Phase one: render

Gdy React tworzy nowe React Element Tree uwzględniające nowy stan, musi sklonować każdy React Element i React Shadow Node dotknięty zmianą. Po sklonowaniu nowe React Shadow Tree jest commitowane.

Renderer React Native wykorzystuje współdzielenie strukturalne, aby zminimalizować narzut niezmienności. Podczas klonowania React Element w celu uwzględnienia nowego stanu, klonowany jest każdy element na ścieżce do korzenia. React klonuje React Element tylko wtedy, gdy wymaga aktualizacji właściwości, stylu lub elementów potomnych. Niezmienione elementy są współdzielone między starym i nowym drzewem.

W powyższym przykładzie React tworzy nowe drzewo za pomocą operacji:

  1. CloneNode(Węzeł 3, {backgroundColor: 'yellow'}) → Węzeł 3'

  2. CloneNode(Węzeł 2) → Węzeł 2'

  3. AppendChild(Węzeł 2', Węzeł 3')

  4. AppendChild(Węzeł 2', Węzeł 4)

  5. CloneNode(Węzeł 1) → Węzeł 1'

  6. AppendChild(Węzeł 1', Węzeł 2')

Po tych operacjach Węzeł 1' reprezentuje korzeń nowego Drzewa Elementów React. Przypiszmy T do "poprzednio renderowanego drzewa" a T' do "nowego drzewa":

Render pipeline 5

Zwróć uwagę, że T i T' współdzielą Węzeł 4. Współdzielenie strukturalne poprawia wydajność i zmniejsza zużycie pamięci.

Faza 2. Zatwierdzenie (Commit)

Faza druga: zatwierdzenie

Po utworzeniu nowego Drzewa Elementów React i Cieniowego Drzewa React, React musi je zatwierdzić.

  • Obliczanie układu: Podobnie jak podczas Renderowania początkowego. Istotna różnica: obliczenia układu mogą wymusić klonowanie współdzielonych Cieniowych Węzłów React. Może się to zdarzyć, jeśli zmiana układu w węźle nadrzędnym wpłynie na układ współdzielonego węzła.

  • Awansowanie drzewa (Nowe drzewo → Następne drzewo): Analogicznie do procesu podczas Renderowania początkowego.

Faza 3. Montaż (Mount)

Faza trzecia: montaż

  • Awansowanie drzewa (Następne drzewo → Renderowane drzewo): Atomowe zastąpienie "poprzednio renderowanego drzewa" przez "następne drzewo", aby faza montowania mogła obliczyć różnicę względem właściwego drzewa.

  • Obliczanie różnic drzew: Oblicza różnice między "poprzednio renderowanym drzewem" (T) a "następnym drzewem" (T'). Wynikiem jest lista atomowych operacji mutacji do wykonania na widżetach hosta.

    • W powyższym przykładzie operacja to: UpdateView(**Node 3**, {backgroundColor: 'yellow'})
    • Różnice można obliczyć dla dowolnego zamontowanego drzewa i dowolnego nowego drzewa. Renderer może pominąć niektóre pośrednie wersje drzewa.
  • Montowanie widoków: Zastosowanie atomowych operacji mutacji do odpowiednich widżetów hosta. W powyższym przykładzie tylko backgroundColor Widoku 3 zostanie zaktualizowany (na żółty).

Render pipeline 6


Aktualizacje stanu w Rendererze React Native

Dla większości informacji w Cieniowym Drzewie, React jest jedynym właścicielem i źródłem prawdy. Wszystkie dane pochodzą z Reacta, a przepływ danych jest jednokierunkowy.

Istnieje jednak ważny wyjątek: komponenty w C++ mogą przechowywać stan nieudostępniony JavaScriptowi, gdzie JavaScript nie jest źródłem prawdy. Ten Stan C++ kontrolowany jest przez C++ i Platformę Hostującą. Zazwyczaj dotyczy to tylko zaawansowanych Komponentów Hosta wymagających Stanu C++. Większość Komponentów Hosta nie potrzebuje tej funkcjonalności.

Przykładowo, ScrollView używa tego mechanizmu do przekazywania rendererowi aktualnego przesunięcia (offset). Aktualizacja jest wyzwalana z platformy hostującej, konkretnie z widżetu reprezentującego komponent ScrollView. Informacja o przesunięciu jest używana w API takim jak measure. Ponieważ ta aktualizacja pochodzi z platformy hostującej i nie wpływa na Drzewo Elementów React, dane stanowe przechowywane są w Stanie C++.

Koncepcyjnie aktualizacje C++ State są podobne do opisanych powyżej aktualizacji stanu React, ale z dwiema istotnymi różnicami:

  1. Pomijają „fazę renderowania”, ponieważ React nie jest w nie zaangażowany.

  2. Aktualizacje mogą być inicjowane i wykonywane na dowolnym wątku, w tym na wątku głównym.

Faza 2. Zatwierdzenie (Commit)

Faza druga: zatwierdzenie

Podczas aktualizacji C++ State, blok kodu żąda aktualizacji ShadowNode (N) poprzez ustawienie C++ State na wartość S. Renderer React Native wielokrotnie próbuje pobrać najnowszą zatwierdzoną wersję N, sklonować ją z nowym stanem S i zatwierdzić N’ w drzewie. Jeśli w tym czasie React lub inna aktualizacja C++ State wykona inne zatwierdzenie, zatwierdzenie C++ State nie powiedzie się, a renderer ponowi próbę wielokrotnie aż do sukcesu. Zapobiega to kolizjom źródła prawdy i wyścigom.

Faza 3. Montaż (Mount)

Faza trzecia: montaż

Faza montażu jest praktycznie identyczna z fazą montażu aktualizacji stanu React. Renderer nadal musi przeliczyć układ, wykonać różnicowanie drzewa itp. Szczegóły znajdziesz w powyższych sekcjach.