React.lazy() pozwala na zastosowanie mechanizmu „lazy loading” poprzez podział kodu aplikacji, co będę chciał przedstawić w dzisiejszym artykule. Dodatkowo w artykule zostanie pokazane działanie React.Suspense, gdyż te dwa elementy wykorzystywane są razem. Zachęcam do prześledzenia przykładu, wtedy wszystko stanie się jasne.

Czym jest lazy loading?

W uproszczeniu, lazy loading możemy określić jako mechanizm / wzorzec, który ma na celu optymalizację aplikacji. Główne elementy aplikacji ładowane są od razu, zaś elementy dodatkowe ładowane są na żądanie, wtedy kiedy są potrzebne. Dobrym przykładem lazy loading może być ładowanie zdjęcia, dopiero wtedy, gdy przeskrolujemy stronę do odpowiedniego miejsca, w którym dane zdjęcie jest widoczne. Czasami nie ma potrzeby ładowania od razu wszystkich zdjęć na stronie.
Innym przykładem może być doładowywanie wyników tabeli, kiedy zeskrolujemy wyniki do ostatniego wiersza…

Jak to ma się do naszych Reactowych aplikacji? Ładując komponent, który zawiera w sobie inne komponenty nie musimy od razu ładować całego kodu, a komponenty które zależą od jakiejś logiki, mogą być doładowywane później, tylko wtedy gdy rzeczywiście dany komponent jest potrzebny. Taki przykład zaprezentuję na sam koniec.

Przejdźmy do bardzo przykładu:
Stworzymy komponent, który wyświetla tekst

function LazyComponent() {
    return (<div>Lazy component</div>)
}

export default LazyComponent;

Teraz w komponencie głównym wyświetlamy nasz komponent

import LazyComponent from './LazyComponent';

function App() {
    return (<div><LazyComponent/></div>);
}

OK. Wszystko działa. Widzimy tekst „Lazy component”. Podstawy Reacta 😉

Spróbujmy użyć teraz React.lazy() aby doładować <LazyComponent />

Zamiast normalnego importu, skorzystamy z React.lazy()

import React from 'react';
const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() {
    return (<div><LazyComponent/></div>);
}

OK. Niestety naszym oczom ukazał się błąd…

Error: A React component suspended while rendering, but no fallback UI was specified.

Add a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display.

Musimy użyć React Suspense oraz fallback, aby wskazać co ma się wyrenderować, kiedy komponenty podrzędne nie są gotowe. W naszym przypadku, komponentem którego nie ma w momencie renderowania jest <LazyComponent />

import React, {Suspense} from 'react';

const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() {
    return (
        <div>
            Test React.lazy() oraz Suspense
            <Suspense fallback={<div>Loading...</div>}>
                <LazyComponent/>
            </Suspense>
        </div>
    );
}

Teraz, do czasu pobrania komponentu <LazyComponent/> na ekranie pojawi się napis „Loading…”. Ciężko przetestować to w warunkach lokalnych, bo wszystko dzieje się zbyt szybko. Natomiast używając np. Google Chrome, w DevTools w zakładce Network możemy zasymulować szybkość łącza. Wybierzmy Slow 3G.

Możemy też sztucznie wymusić opóźnienie z poziomu kodu:

const LazyComponent = React.lazy(() => {
    return new Promise(resolve => setTimeout(resolve, 5000)).then(
        () => import('./LazyComponent')
    );
});

Na koniec prześledźmy przykład z kilkoma komponentami doładowanymi w zależności od jakiejś prostej logiki. W naszym przykładzie utworzymy 3 komponenty <Empty /> <Male /> oraz <Female />. Każdy z nich będzie wyświetlał właściwy tekst – „Empty”, „Male” oraz „Female”.
Przy testowaniu tego przykładu polecam otworzyć DevTools, ustawić szybkość sieci na Slow 3G oraz uważnie śledzić jakie pliki pobierane są po kliknięciu w przycisk.

import React, {Suspense} from 'react';

const Empty = React.lazy(() => import('./Empty'));
const Male = React.lazy(() => import('./Male'));
const Female = React.lazy(() => import('./Female'));

function App() {
    const [counter, setCounter] = React.useState(0);

    return (
        <div>
            <button onClick={() => setCounter(counter + 1)}>Increment</button>
            <Suspense fallback={<div>Loading...</div>}>
                {1 === counter && <Empty />}
                {2 === counter && <Male />}
                {3 === counter && <Female />}
            </Suspense>
        </div>
    );
}

Prosta logika dla aplikacji wygląda następująco. Domyślny stan licznika wynosi 0. Dla licznika == 1, renderujemy komponent <Empty />, dla licznika == 2, komponent <Male />, dla licznika == 3, komponent <Female />.

Kliknięcie w przycisk „Increment” zwiększa stan licznika.

Jak widać, 3.chunk.js, 1.chunk.js oraz 2.chunk.js doładowywane są w momencie kliknięcie na przycisk. Wszystko działa poprawnie!

Dzięki za Twój czas. Mam nadzieję, że chociaż trochę przybliżyłem podstawy React.lazy() oraz React Suspense.


Źródła:
Obraz: https://unsplash.com/photos/bNhCzmKZ_dI
React lazy oraz React Suspense – oficjalna dokumentacja React: https://reactjs.org/docs/code-splitting.html