Przejdź do treści głównej
Wersja: 0.82

Przegląd wydajności

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 →

Kluczowym powodem wyboru React Native zamiast narzędzi opartych na WebView jest osiągnięcie co najmniej 60 klatek na sekundę i zapewnienie natywnego wyglądu oraz działania aplikacji. Gdzie to możliwe, React Native automatycznie optymalizuje wydajność, pozwalając skupić się na funkcjonalności bez martwienia się o wydajność. Istnieją jednak obszary, w których nie osiągnęliśmy jeszcze tego poziomu, oraz takie, gdzie React Native (podobnie jak pisanie natywnego kodu) nie może samodzielnie wybrać optymalnego podejścia. W takich przypadkach konieczna jest ręczna interwencja. Dążymy do zapewnienia płynnego działania interfejsu domyślnie, ale czasem może to być niemożliwe.

Ten przewodnik nauczy Cię podstaw diagnozowania problemów z wydajnością oraz omówi częste źródła problemów i proponowane rozwiązania.

Co musisz wiedzieć o klatkach

Pokolenie Twoich dziadków nazywało filmy "ruchomymi obrazami" nie bez powodu: realistyczny ruch wideo to iluzja stworzona przez szybkie zmiany statycznych obrazów w stałym tempie. Każdy taki obraz nazywamy klatką. Liczba klatek wyświetlanych na sekundę bezpośrednio wpływa na płynność i naturalność wideo (lub interfejsu). Urządzenia z iOS i Android wyświetlają co najmniej 60 klatek na sekundę, co daje Tobie i systemowi UI maksymalnie 16.67 ms na wykonanie pracy potrzebnej do wygenerowania statycznego obrazu (klatki). Jeśli nie zdążysz wykonać tej pracy w wyznaczonym czasie, nastąpi "utrata klatki", a interfejs stanie się mniej responsywny.

Aby nieco skomplikować sprawę, otwórz Menu deweloperskie w swojej aplikacji i aktywuj Show Perf Monitor. Zauważysz, że istnieją dwa różne wskaźniki klatek.

Zrzut ekranu monitora wydajności

Klatki JS (wątek JavaScript)

W większości aplikacji React Native logika biznesowa działa w wątku JavaScript. To miejsce, gdzie żyje Twoja aplikacja React, wykonywane są wywołania API, przetwarzane zdarzenia dotykowe itp. Aktualizacje widoków natywnych są grupowane i przesyłane na stronę natywną po każdej iteracji pętli zdarzeń, przed upływem limitu czasu klatki (jeśli wszystko pójdzie dobrze). Jeśli wątek JavaScript nie odpowiada podczas klatki, zostanie ona uznana za utraconą. Przykładowo, ustawienie nowego stanu w głównym komponencie złożonej aplikacji może skutkować kosztownym przeliczaniem poddrzew komponentów - może to zająć 200ms i spowodować utratę 12 klatek. Animacje sterowane przez JavaScript zamarzną na ten czas. Przy dużej liczbie utraconych klatek użytkownik wyraźnie to odczuje.

Przykładem jest reakcja na dotyk: jeśli wątek JavaScript jest zajęty pracą rozłożoną na wiele klatek, możesz zauważyć opóźnienie w reakcji komponentu TouchableOpacity. Dzieje się tak, ponieważ wątek JavaScript jest zajęty i nie może przetworzyć surowych zdarzeń dotykowych z głównego wątku. W efekcie TouchableOpacity nie zareaguje na dotyk i nie zaktualizuje przezroczystości natywnego widoku.

Klatki UI (główny wątek)

Zauważyłeś pewnie, że natywne stack navigatory (jak @react-navigation/native-stack z React Navigation) domyślnie działają płynniej niż te oparte na JavaScript. To dlatego, że animacje przejść wykonują się w głównym wątku UI, więc nie są przerywane przez utraty klatek w wątku JavaScript.

Podobnie możesz płynnie przewijać ScrollView nawet przy zablokowanym wątku JavaScript, ponieważ ScrollView działa w głównym wątku. Zdarzenia przewijania są wysyłane do wątku JS, ale ich odbiór nie jest konieczny do samego przewijania.

Częste źródła problemów z wydajnością

Tryb deweloperski (dev=true)

Wydajność wątku JavaScript znacząco spada podczas pracy w trybie deweloperskim (dev=true). Jest to nieuniknione: środowisko wykonuje znacznie więcej pracy w czasie rzeczywistym, aby zapewnić pomocne ostrzeżenia i komunikaty o błędach. Zawsze testuj wydajność w wersjach produkcyjnych.

Używanie instrukcji console.log

W spakowanej aplikacji te instrukcje mogą powodować poważne wąskie gardło w wątku JavaScript. Dotyczy to również wywołań z bibliotek debugujących takich jak redux-logger, dlatego pamiętaj o ich usunięciu przed pakowaniem. Możesz także użyć tej wtyczki Babel, która usuwa wszystkie wywołania console.*. Najpierw zainstaluj ją poleceniem npm i babel-plugin-transform-remove-console --save-dev, a następnie edytuj plik .babelrc w katalogu projektu w następujący sposób:

json
{
"env": {
"production": {
"plugins": ["transform-remove-console"]
}
}
}

To automatycznie usunie wszystkie wywołania console.* w wersjach produkcyjnych twojego projektu.

Zaleca się używanie wtyczki nawet jeśli w twoim projekcie nie ma wywołań console.*. Mogą je wykonywać również biblioteki stron trzecich.

Zbyt wolne renderowanie FlatList lub słaba wydajność przewijania dużych list

Jeśli twoja FlatList renderuje się zbyt wolno, upewnij się że zaimplementowałeś getItemLayout, aby zoptymalizować prędkość renderowania poprzez pominięcie pomiarów renderowanych elementów.

Istnieją także inne biblioteki list optymalizowane pod kątem wydajności, w tym FlashList i Legend List.

Spadki FPS wątku JS spowodowane intensywną pracą na wątku JavaScript

Najczęściej objawia się to jako "wolne przejścia nawigatora", ale może występować także w innych sytuacjach. Użycie InteractionManager może być dobrym rozwiązaniem, ale jeśli opóźnianie pracy podczas animacji zbytnio pogarsza doświadczenia użytkownika, rozważ zastosowanie LayoutAnimation.

Animated API obecnie oblicza każdą klatkę kluczową na żądanie na wątku JavaScript, chyba że ustawisz useNativeDriver: true, podczas gdy LayoutAnimation wykorzystuje Core Animation i nie jest podatne na spadki klatek w wątku JS ani głównym wątku.

Przykładem zastosowania jest animowanie modala (zsuwanie z góry i wypełnianie półprzezroczystym tłem) podczas inicjalizacji i odbierania odpowiedzi z kilku żądań sieciowych, renderowania zawartości modala oraz aktualizacji widoku z którego modal został otwarty. Więcej informacji na temat używania LayoutAnimation znajdziesz w przewodniku po animacjach.

Ostrzeżenia:

  • LayoutAnimation działa tylko dla animacji typu "wystrzel i zapomnij" (animacje "statyczne") — jeśli animacja musi być przerywalna, będziesz musiał użyć Animated.

Przesuwanie widoku na ekranie (przewijanie, przesunięcie, obrót) powoduje spadki FPS w głównym wątku

Szczególnie dotyczy to Androida, gdy masz tekst z przezroczystym tłem nałożony na obraz lub w innych sytuacjach wymagających kompozycji alfa do ponownego rysowania widoku w każdej klatce. Włączenie renderToHardwareTextureAndroid może w tym znacząco pomóc. Dla iOS domyślnie włączone jest już shouldRasterizeIOS.

Uważaj, aby nie nadużywać tej funkcji, bo zużycie pamięci może gwałtownie wzrosnąć. Testuj wydajność i zużycie pamięci przy używaniu tych właściwości. Jeśli widok nie będzie już przemieszczany, wyłącz tę właściwość.

Animowanie rozmiaru obrazu powoduje spadki FPS w głównym wątku

W systemie iOS, za każdym razem gdy zmieniasz szerokość lub wysokość komponentu Image, obraz jest ponownie przycinany i skalowany z oryginału. To może być bardzo kosztowne, szczególnie przy dużych obrazach. Zamiast tego używaj właściwości stylu transform: [{scale}] do animowania rozmiaru. Przykładem zastosowania może być dotknięcie obrazu i jego powiększenie na pełny ekran.

Mój komponent TouchableX nie reaguje płynnie

Czasem, gdy wykonujemy akcję w tej samej klatce, w której zmieniamy przezroczystość lub podświetlenie komponentu reagującego na dotyk, efekt nie będzie widoczny dopóki funkcja onPress nie zakończy działania. Może się to zdarzyć, gdy onPress ustawia stan powodujący kosztowne przerenderowanie i opuszczenie kilku klatek. Rozwiązaniem jest opakowanie akcji wewnątrz handlera onPress w requestAnimationFrame:

tsx
function handleOnPress() {
requestAnimationFrame(() => {
this.doExpensiveAction();
});
}