Strona główna / Artykuły

Tworzenie czytnika PDF odpornego na błędy do dużych dokumentów w React Native

Przyswoj architekturę do renderowania ogromnych PDF-ów liczących tysiące stron w React Native, wykorzystującą opóźnione drzewka zakładek, funkcję wyszukiwania oraz stabilną nawigację na urządzeniach o niskich specyfikacjach.

Wydawca: REACTAPP.TOP12 min czytania2758 słów

Źródła: Rendering and Navigating Large PDFs in React Native Without Crashing the App

Czteropagowy plik PDF można z łatwością wyświetlić na ekranie w aplikacji React Native. Dokument składający się z setek lub tysięcy stron, zawierający głęboko zagnieżdżone zakładki oraz łącza między poszczególnymi sekcjami stanowi zupełnie inny wyzwanie – może szybko stać się powolny, nieodpowiedni na interakcje lub wręcz podatny na awarie na mniej wydajnym sprzęcie. Ten przewodnik przedstawia architekturę, która bezpośrednio adresuje to wyzwanie: efektywne renderowanie dużych plików PDF, szybkie przechodzenie do dowolnych stron, przeglądanie i wyszukiwanie zagnieżdżonych drzew zakładek oraz utrzymanie stabilności całego doświadczenia nawet na urządzeniach o ograniczonych możliwościach.

W jednym z testów użyto pliku PDF liczącego ponad 1 900 stron oraz drzewa zakładek z wieloma poziomami zagnieżdżenia. Próba rozszerzenia całego tego drzewa jednocześnie powodowała poważne obciążenie pamięci. Poniższe sekcje opisują podejście, które rozwiązało te problemy.

Czym react-native-pdf się zajmuje — i czym nie

Zanim zdecydujesz się na dodatkowe biblioteki, przydatne jest dokładne poznanie granic wbudowanych możliwości react-native-pdf, ponieważ to one określają dalszy kierunek projektowania:

  • Dzięki swojemu callbackowi onLoadComplete faktycznie udostępnia surowe informacje o zakładkach (przez parametr tableContents) na iOS i Android — ale nie na Windows, a z pewnością nie w formie, którą można by bezpośrednio wyświetlić na ekranie. Są to surowe, niestrukтуrowane dane, które nadal wymagają przetwarzania i renderowania po stronie użytkownika.
  • Faktycznie oferuje uniwersalny callback do obsługi kliknięć w linki (onPressLink), ale ten callback nie potrafi odróżnić skoku wewnątrz dokumentu od zewnętrznego adresu URL, a także nie dostarcza w sposób pewny numeru strony docelowej dla linków wewnętrznych.
  • Zawiera brak wbudowanego wyszukiwania pełnego tekstu wewnątrz dokumentu. Nie jest to wada charakterystyczna wyłącznie dla tej biblioteki — rzeczywiste wyszukiwanie tekstu w dokumencie praktycznie nie istnieje obecnie w środowisku open-source React Native PDF. Nieliczne narzędzia, które je obsługują, z Nutrient (wcześniej znane jako PSPDFKit) jako najbardziej rozpoznawalnym przykładem, to komercyjne SDK z kosztami licencji za każdą platformę, które znacznie przekraczają możliwości finansowe większości małych zespołów lub projektów osobistych.
  • Biorąc to pod uwagę, praktycznym sposobem na uzyskanie niezawodnych danych o zakładkach i linkach między platformami jest pobieranie ich niezależnie, zamiast polegać na surowym wyniku funkcji zwrotnej renderera — dokładnie tę rolę pełni pdf-lib, o którym będzie mowa dalej. Ta architektura nie próbuje wyszukiwać tekstu w samym treści PDF; zajmuje się natomiast wyszukiwaniem tytułów zakładek, co jest znacznie prostszym zadaniem, które można w pełni zrealizować za pomocą narzędzi open-source.

    Wybór pakietów

    Ustawienie to opiera się na trzech pakietach, z których każdy jest odpowiedzialny za jedno konkretne zadanie:

    react-native-pdf obsługuje wyświetlanie poprzez używanie wbudowanego silnika PDF platformy zamiast konwertowania stron na obrazy oparte na JavaScript — to właśnie ten natywny podejście zapewnia umiarkowane zużycie pamięci przy pracy z dużymi plikami. Jest odpowiedzialny za pokazywanie stron, obsługę powiększania, wywoływanie zdarzeń zmiany strony oraz umożliwienie programowego przechodzenia do konkretnej strony.

    react-native-blob-util to zależność, od której sam react-native-pdf polega w celu uzyskania dostępu do plików — zajmuje się pobieraniem, przechowywaniem w pamięci cache oraz odczytywaniem wyświetlanego PDF. Nie jest to dodatek opcjonalny; stanowi wymóg wbudowany w sposób działania react-native-pdf na poziomie interno.

    pdf-lib eliminuje wskazaną wcześniej lukę. Zamiast renderować cokolwiek, umożliwia bezpośrednie czytanie i modyfikowanie wewnętrznej struktury PDF: katalogu dokumentów, drzewa stron oraz — co jest kluczowe w tym przypadku — słownika struktury, w którym faktycznie znajdują się zakładki wewnątrz PDF, a także obiektów adnotacji linków przypisanych do poszczególnych stron, gdzie przechowywane są zarówno linki wewnętrzne, jak i zewnętrzne. Ponieważ react-native-pdf nie prezentuje żadnych z tych elementów w sposób uporządkowany i wiarygodny, pdf-lib służy do jednorazowego przeszukania tej wewnętrznej struktury i utworzenia czystej reprezentacji w formacie JSON drzewa zakładek — tytułu, strony docelowej oraz zagnieżdżonych elementów — wraz ze zestawem rozwiązanych destynacji linków, wszystko to niezależnie od specyficznej dla platformy zachowania natywnego renderera. Ten krok wydobywania danych powinien zostać wykonany tylko raz rath

    ponadto, w każdym renderowaniu – kwestia omówiona bardziej szczegółowo w Kroku 6.

    Uwaga dotycząca Expo: react-native-pdf zależy od modułów natywnych, więc nie będzie działać w standardowym kliencie Expo Go. Wymagane jest własne środowisko rozwojowe lub projekt typu bare/native.

    Architektura

    System jest zorganizowany w trzy warstwy, z których każda jest odseparowana od wewnętrznego funkcjonowania pozostałych:

    • Przeglądarka PDF – śledzi aktualną stronę, łączną liczbę stron, stan ładowania oraz ewentualne błędy.
    • Pasek narzędzi czytelnika – pokazuje informacje o aktualnej stronie i umożliwia dostęp do panelu zakładek.
    • Panel zakładek – zarządza polem wyszukiwania, rozbudowalnym drzewem zakładek (tworzonym na podstawie danych wydobytych za pomocą pdf-lib) i przekazuje jedną informację: którą stronę wybrał użytkownik.

    Paneel zakładek nie ma pojęcia o tym, w jaki sposób sam plik PDF jest renderowany, a przeglądarka PDF nie wie, jak implementowana jest funkcja wyszukiwania zakładek. Ten jednokierunkowy proces — panel wybiera stronę, a ekran poleca przeglądarce przejście na nią — sprawia, że działania takie jak wpisywanie tekstu do pola wyszukiwania lub rozszerzanie węzła drzewa nie powodują kosztownego ponownego renderowania samego komponentu PDF.

    Krok 1: Ustrukturyzowanie danych zakładek

    Każdy zakładka, z którym pracujesz, wymaga trzech elementów: tytułu, strony docelowej oraz opcjonalnego zbioru podzakładek — prostej struktury rekurencyjnej, która naturalnie odpowiada temu, co zwraca krok wydobywania danych z pdf-lib. Najważniejszą kwestią jest to, że każda zakładka musi mieć stabilny, unikalny identyfikator, a tym identyfikatorem nie może być po prostu jej pozycja w zbiorze. Gdy użytkownik zaczyna wpisywać tekst w polu wyszukiwania lub zmienia stan otwartych/zamkniętych gałęzi, pozycja danej zakładki na wyświetlanej liście ulega zmianie. Jeśli polegasz na indeksie w zbiorze jako identyfikatorze, React traci możliwość odróżnienia jednego wiersza od drugiego, stan rozszerzania/zamykania zostaje przypisany niewłaściwemu węzłowi, a FlatList zaczyna błędnie recyklingować wiersze.

    Krok 2: Wyszukiwanie bez utraty kontekstu

    Ponieważ pasujący zakładka jest często umieszczany na kilku poziomach wewnątrz drzewa, logika wyszukiwania musi przeszukiwać strukturę rekurencyjnie, a nie tylko tytuły najwyższego poziomu. Równie ważne jest to, że gdy dowolny węzeł potomny odpowiada terminowi wyszukiwania, należy uwzględnić również jego węzeł nadrzędny w wynikach, nawet jeśli sam tytuł tego węzła nadrzędnego nie odpowiada niczemu. Jeśli to pominąć, wynik znajdujący się głęboko w strukturze pojawi się bez żadnej informacji o tym, gdzie dokładnie znajduje się w dokumencie.

    Aby zachować dobrą responsywność, otocz logikę filtrowania hookiem memoizacji, dzięki czemu będzie ona uruchamiana ponownie tylko wtedy, gdy zmieni się ciąg wyszukiwania lub dane zakładki. W przypadku drzew z dużą liczbą wpisów dodaj również mechanizm debouncingu do pola tekstowego, aby uniknąć ponownego filtrowania całej struktury przy każdym naciśnięciu klawisza.

    Krok 3: Spłaszczenie tylko widocznych węzłów

    FlatList jest odpowiednim narzędziem ze względu na wydajność, ale wymaga prostego tablicy, a nie zagnieżdżonej struktury drzewiastej. Dlatego potrzebny jest proces przeglądania drzewa zakładek i tworzenia spłaszczonej tablicy zawierającej jedynie węzły, które powinny być obecnie widoczne — dzieci węzła są uwzględniane tylko wtedy, gdy dany węzeł jest obecnie rozwinięty. Każda spłaszczona pozycja powinna również zawierać wartość depth, aby można było określić, o ile należy ją wizualnie wciągnąć.

    To kroku można uznać za najważniejszą decyzję dotyczącą wydajności w całej implementacji. Gdybyś spłaszczył cały drzewo i po prostu ukrył wiersze należące do złożonych gałęzi, FlatList nadal przechowywałby tysiące wierszy, które nigdy nie pojawiają się na ekranie. Dzięki spłaszczaniu jedynie tych elementów, które są obecnie rozwinięte, drzewo zakładek z tysiącami pozycji może w danym momencie przekazać FlatList zaledwie kilkadziesiąt wierszy.

    Krok 4: Stworzenie panelu zakładek

    Zaimplementuj panel jako modalne okno umieszczone nad przeglądarką PDF, a nie jako ekran, który ją zastępuje, oraz ogranicz jego wysokość do około 80% rozdzielczości ekranu, zamiast pozwalać mu zajmować cały ekran. Dzięki temu poniżej panela pozostaje widoczny fragment PDF, który ułatwia orientację, a także jest miejsce na wyraźną кнопkę zamknięcia, zamiast sprawiać wrażenie zupełnie oddzielnego ekranu.

    Wewnątrz modalu powinno znajdować się: pole wyszukiwania połączone z logiką z kroku 2; FlatList wyświetlający uporządkowane wiersze utworzone w kroku 3, skonfigurowany z atrybutami okna — initialNumToRender, maxToRenderPerBatch, windowSize oraz removeClippedSubviews — tak aby renderowano tylko te wiersze znajdujące się w obszarze widoczności; oraz stan domyślny dla przypadku, gdy wyszukiwanie nie przynosi żadnych wyników. Każdy wiersz powinien mieć dwa oddzielne elementy do kliknięcia: kliknięcie treści wiersza wybiera daną stronę i zamyka panel, natomiast kliknięcie małej ikony rozszerzenia przełącza jej zawartość, dzięki czemu otwieranie gałęzi nie powoduje przypadkowego przenoszenia między stronami. Każdy komponent wiersza należy otoczyć tagiem memo, aby rozszerzanie jednej gałęzi nie zmuszało do ponownego renderowania całej listy. Ponadto, gdy pole wyszukiwania nie jest puste, automatycznie należy rozszerzyć każdą odpowiadającą mu gałąź.

    zapobiega to konieczności przeglądania skompresowanych węzłów, aby znaleźć dokładnie ten wynik, który użytkownik szukał.

    Krok 5: Stworzenie ekranu czytnika PDF

    Tutaj odbywa się faktyczne renderowanie za pomocą komponentu Pdf. Użyj funkcji callback przy zmianie strony, aby śledzić aktualną stronę i całkowitą liczbę stron, wyświetlać wbudowany wskaźnik ładowania biblioteki podczas wczytywania dokumentu oraz skonfiguruj funkcję callback przy błędzie, tak aby w przypadku niepowodzenia ekran pokazywał wyraźną wiadomość „nie udało się otworzyć tego dokumentu” wraz z opcją ponownej próby — nie pozwól, by ikona ładowania kręciła się bez końca bez żadnego rozwiązania awaryjnego.

    Gdy tylko przejdziesz na stronę z zakładki, ogranicz numer strony docelowej tak, aby mieścił się w zakresie od 1 do znanej całkowitej liczby stron. Dzięki temu unikniesz sytuacji, gdy uszkodzony lub przestarzały adres — czy to pochodzący z samych danych zakładki, czy z rozwiązanej wewnętrznej linki — spróbuje skierować użytkownika na numer strony, który po prostu nie istnieje. Warto również włączyć cacheowanie pliku PDF, aby dokument już pobraany nie był pobierany na nowo za każdym razem, gdy otwiera się ta strona.

    Jeśli chodzi o styl wizualny, zależy on oczywiście od własnego systemu projektowego twojej aplikacji — ale pod względem strukturalnym kluczowe dla użyteczności są: utrzymanie wysokości panelu na poziomie około 80%, dostarczenie oczywistego sposobu na jego zamknięcie, zapewnienie każdej wierszowi (zwłaszcza ikonie rozszerzenia) odpowiednio dużego obszaru do kliknięcia oraz stworzenie spokojnego, wyraźnie rozpoznawalnego stanu błędu.

    Krok 6: Wyodrębnianie tylko raz, a nie przy każdym renderowaniu

    Krok wyodrębniania za pomocą pdf-lib, o którym mówiono wcześniej, powinien zostać wykonyany dokładnie jednorazowo, poza cyklem renderowania — a nie wewnątrz komponentu, który jest ponownie renderowany za każdym razem, gdy użytkownik wykonuje wyszukiwanie lub rozwija węzeł. Zamiast obliczać ten plik JSON wielokrotnie, należy go zapisać do pamięci podręcznej, przechowywać w stanie React tylko lżejsze pola (identyfikatory, tytuły, numery stron), unikać przechowywania surowych danych w formacie PDF w stanie oraz usuwać wszelkie tymczasowe zasoby po rozmontowaniu ekranu. Jeśli Twój backend może wykonać ten krok wyodrębniania, jest to zazwyczaj lepsze rozwiązanie — aplikacja mobilna będzie wtedy musiała otrzymać jedynie niewielki plik JSON.

    Krok 7: Zachowanie pozycji użytkownika

    Zamknięcie panelu zakładek lub obrót urządzenia nie powinno przywracać czytnika na pierwszą stronę. Przynajmniej należy zachować aktualną stronę, najnowszy rozwinięty gałąź zakładki oraz pozycję przewijania panelu. Zachowuj te wartości na poziomie głównego ekranu lub stanu nawigacji, a nie wewnątrz samego komponentu panelu zakładek — w ten sposób pozostaną dostępne nawet w przypadku usunięcia panelu.

    Krok 8: Grzeczne radzenie sobie z błędami

    Planuj na rzeczywiste awarie, a nie tylko na pomyślny przebieg: URL plików, które są brakujące lub wygasłe, dokumenty uszkodzone lub zabezpieczone hasłem, urządzenia z małą ilością miejsca na przechowywanie, połączenia sieciowe, które przerywają się w trakcie pobierania, zakładki wskazujące na nieważne strony lub linki, które po prostu się nie otwierają. Bez względu na to, co się stanie, unikaj sytuacji, gdy użytkownik musi patrzeć na ikonę spinacza, która nigdy się nie zakończy. Jeśli dokument nie zawiera zakładek, powiedz o tym wprost — na przykład: „Ten dokument nie zawiera zakładek”. Jeśli wyszukiwanie nic nie zwraca, pokaż komunikat „Nie znaleziono zakładek”. Dobrze zdefiniowany stan pusty lub błędowy jest o wiele lepszy niż ekran, który wygląda tak, jakby utknął.

    Krok 9: Testowanie rzeczywistych scenariuszy awarii

    Nie weryfikuj swojej implementacji na podstawie jednego wygodnego pliku PDF. Rozszerz testy o następujące aspekty:

    • Dokumenty: pliki bez żadnych zakładek, pliki liczące setki stron, głęboko zagnieżdżone hierarchie zakładek, duże wmontowane obrazy, ochrona hasłem, celowe uszkodzenie danych, linki wewnętrzne, linki zewnętrzne oraz tytuły zawierające specjalne znaki
    • Nawigacja: przechodzenie do zakładek na poziomie korzenia lub głęboko zagnieżdżonych, do pierwszej i ostatniej strony, do numeru strony poza zakresem, ponowne otwieranie panelu po jego zamknięciu, wyszukiwanie elementu zagnieżdżonego oraz wyszukiwanie, które nie daje żadnych wyników
    • Urządzenia: telefon Android z małą ilością pamięci, nowoczesne urządzenie flagowe, zarówno iPhone, jak i iPad, tryb pionowy i poziomy oraz przejścia między tłem a pierwszym planem
  • Warunki sieciowe: praca offline z już załadowanym w pamięci tymczasowej dokumentem, utrata połączenia w trakcie pobierania, ponowna próba po przywróceniu połączenia oraz wygasły lub niedostępny adres URL pliku zdalnego
  • Tutaj testowanie na rzeczywistym sprzęcie ma większe znaczenie niż przy większości zadań związanych z funkcjonalnościami — emulator po prostu nie będzie w stanie odtworzyć presji na pamięć i ograniczeń przechowywania, które występują w rzeczywistym urządzeniu budżetowym.

    Często zadawane pytania

    Dlaczego po prostu nie renderować każdej strony jako obrazka? Natywny silnik renderowania wyświetla tylko treść aktualnie potrzebną na ekranie, zamiast zmuszać React do zarządzania oddzielnym komponentem obrazka dla każdej z tysiąca stron.

    Dlaczego używać pdf-lib zamiast danych zakładek dostarczanych już przez react-native-pdf? Ponieważ te wbudowane dane są nieprzetworzone, zachowują się niespójnie na różnych platformach (Windows w ogóle nic nie zwraca) i nie oferują wiarygodnego sposobu na rozwiązywanie problemów z adresami linków wewnętrznych. pdf-lib analizuje bezpośrednio strukturę zarysu oraz adnotacji linków w pliku PDF, w jednej operacji, dostarczając czyste, przewidywalne dane, nad którymi masz pełną kontrolę.

    Czy możliwa jest wyszukiwanie pełnego tekstu w samym dokumencie? Na razie nie przy użyciu wyłącznie narzędzi open source – ta funkcja jest obecnie przeznaczona dla komercyjnych SDK. Opisana tutaj metoda obejmuje wyszukiwanie według tytułów zakładek, co jest bardziej ograniczonym problemem, który możesz w pełni rozwiązać samodzielnie.

    Czy każda gałąź w drzewie zakładek powinna się automatycznie rozwijać? Nie — należy rozwijać tylko tę gałąź, którą użytkownik aktywnie otwiera. Na urządzeniach o ograniczonej pamięci warto ograniczyć interfejs tak, aby w danym momencie mogła być rozwinięta tylko jedna gałąź na poziomie korzenia.

    Czy cokolwiek z tego gwarantuje, że aplikacja nigdy się nie zawali? Żaden podejście nie może zagwarantować tego w przypadku każdej możliwej kombinacji dokumentu i urządzenia. Celem jest zmniejszenie ryzyka: unikanie szybkiego renderowania, utrzymywanie minimalnego stanu przechowywanego, pobieranie danych tylko raz, wirtualizacja listy zakładek oraz wyraźne radzenie sobie z każdym modelem awarii.

    Ostateczne uwagi

    Osiągnięcie odpowiedniej wydajności nie polega na wyborze jednej biblioteki — react-native-pdf zajmuje się renderowaniem, pdf-lib dostarcza wiarygodnych informacji o zakładkach i linkach, a prawdziwa stabilność pochodzi od sposobu zarządzania stanem, wirtualizacją oraz obsługą błędów w związku z obiema tymi bibliotekami.

    Praktyczne zasady, których należy przestrzegać:

    1. Zrealizuj wyodrębnianie zakładek i linków raz za pomocą pdf-lib, nigdy jako część procesu renderowania.
    2. Nigdy nie renderuj całego drzewa zakładek jednocześnie — spłaszcz i wyświetl tylko rozszerzone części.
    3. Zastosuj wirtualizowany FlatList do przechowywania listy zakładek.
    4. Całkowicie unikaj przechowywania surowych danych w postaci bajtów PDF w stanie React.
    5. Zwaliduj cel strony przed przechodzeniem do niej.
    6. Zachowaj tylko minimalny stan potrzebny do przywrócenia pozycji czytelnika.
  • Testowanie z wykorzystaniem naprawdę dużych dokumentów na urządzeniach o ograniczonych zasobach pamięci.
  • Zawsze pokazuj wyraźny komunikat błędu lub stan pusty — nigdy nie pozostawaj na ekranie nieskończonego obrotowego ikony ładowania.
  • Literatura pokrewna

  • React Native, Flutter i inne: aplikacje mobilne wieloplatformowe w 2026 roku — Porównawcze omówienie React Native, Flutter, Kotlin Multiplatform, Ionic, NativeScript oraz PWA pod kątem wydajności, doświadczenia programisty i stopnia rozwoju ekosystemu.
  • Tworzenie konfigurowalnego komponentu wyboru dla React Native Paper — Przewodnik po projektowaniu i udostępnieniu kodu źródłowego komponentu react-native-paper-select, obejmujący funkcje wyszukiwania, wielokrotnego wyboru elementów, list podzielonych na sekcje oraz kwestie związane z wydajnością.
  • Wycieki pamięci w React Native: śledzenie właścicieli pamięci JS i natywnej — Dowiedz się, dlaczego zbieranie śmieci nie może uratować aplikacji React Native przed wyciekami pamięci natywnej, oraz jak odkryć, co utrzymuje w życiu funkcje zwrotne, obiekty JSI i odszyfrowane obrazy.
  • Niestabilne zależności useEffect: diagnozowanie zużycia baterii w React Native — Zobacz, jak zależność od obiektu i brak tablicy zależności spowodowały zużycie baterii i problemy z renderowaniem w React Native, jak to skontrolować oraz trzy rozwiązania, które zadziałały.