Dzielenie kodu
Pakowanie
Większość reactowych aplikacji będzie “dołączała” swoje pliki poprzez narzędzia takie jak Webpack, Rollup czy Browserify. Pakowanie to proces śledzenia zaimportowanych plików i łączenia ich w pojedynczy plik tzw. “bundle” (pol. paczka). Taka paczka może zostać umieszczona na stronie w celu załadowania całej aplikacji naraz.
Przykład
Kod aplikacji:
// app.js
import { add } from './math.js';
console.log(add(16, 26)); // 42
// math.js
export function add(a, b) {
return a + b;
}
Paczka:
function add(a, b) {
return a + b;
}
console.log(add(16, 26)); // 42
Uwaga:
Twoje paczki prawdopodobnie będą się znacząco różnić.
Jeśli używasz Create React App, Next.js, Gatsby lub innego podobnego narzędzia, Webpack powinien być już skonfigurowany i gotowy do użytku.
Jeśli nie, musisz skonfigurować Webpacka samodzielnie. Przykłady znajdziesz w przewodniku po instalacji oraz w sekcji pt. “Podstawowe informacje” zawartych w dokumentacji Webpacka.
Dzielenie kodu
Tworzenie paczek jest świetne, ale wraz ze wzrostem objętości kodu twojej aplikacji, rośnie również objętość paczek. Zwłaszcza gdy dołączasz do projektu duże, zewnętrzne biblioteki. Musisz pilnować, aby twój pakiet nie był zbyt duży, ponieważ wtedy aplikacja będzie się długo ładowała.
Aby uniknąć problemu zbyt dużego pakietu, warto już na początku o tym pomyśleć i rozpocząć “dzielenie” swojej paczki. Dzielenie kodu to funkcja wspierana przez narzędzia takie jak Webpack, Rollup oraz Browserify (przez factor-bundle), które mogą tworzyć wiele pakietów doładowywanych dynamicznie w czasie wykonania kodu aplikacji.
Dzielenie kodu twojej aplikacji ułatwi ci użycie “leniwego ładowania” do wczytywania jedynie tych zasobów które są aktualnie wymagane przez użytkownika zasobów, co może znacznie poprawić wydajność twojej aplikacji. Mimo że nie zmniejszysz w ten sposób sumarycznej ilości kodu, unikniesz ładowania funkcjonalności zbędnych dla użytkownika w danym momencie, co przełoży się na mniejszą ilość kodu do pobrania na starcie aplikacji.
import()
Najlepszym sposobem na wprowadzenie podziału kodu do twojej aplikacji jest użycie dynamicznego wariantu funkcji import()
.
Przed:
import { add } from './math';
console.log(add(16, 26));
Po:
import("./math").then(math => {
console.log(math.add(16, 26));
});
Gdy Webpack natknie się na taką składnię, automatycznie zacznie dzielić kod w twojej aplikacji. Jeśli używasz Create React App, posiadasz już gotową konfigurację i możesz natychmiast zacząć z niej korzystać. Jest ona również obsługiwana domyślnie przez Next.js.
Jeśli konfigurujesz Webpacka samodzielnie, zalecamy przeczytać przewodnik po dzieleniu kodu. Twoja konfiguracja Webpacka powinna wyglądać podobnie do tej.
Kiedy używasz Babela, musisz się upewnić, że jest on w stanie sparsować składnię dynamicznego importu, ale jej nie przekształca w żaden sposób. W tym celu skorzystaj z pluginu @babel/plugin-syntax-dynamic-import.
React.lazy
Uwaga:
React.lazy
iSuspense
nie są jeszcze dostępne dla renderowania po stronie serwera. Jeśli chcesz dzielić kod dla aplikacji renderowanej na serwerze, sugerujemy skorzystać z pakietu Loadable Components.
Ma on przystępną instrukcję dzielenia pakietów przy renderowaniu po stronie serwera.
Funkcja React.lazy
pozwala renderować dynamicznie importowane komponenty jak zwykłe komponenty.
Przed:
import OtherComponent from './OtherComponent';
Po:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
Powyższy kod automatycznie załaduje paczkę zawierającą OtherComponent
podczas pierwszego renderowania komponentu.
React.lazy
jako argument przyjmuje funkcję, która wywołuje dynamiczny import()
.
Musi ona zwrócić obiekt (Promise
) (pol. obietnicę), która rozwiązuje się do modułu z eksportem domyślnym (default
) zawierającym
komponent reactowy.
“Leniwy” komponent powinien zostać wyrenderowany wewnątrz Suspense
, dzięki któremu na czas ładowania możemy wyświetlić komponent zastępczy (np. wskaźnik ładowania).
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Wczytywanie...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
Właściwość fallback
tego komponentu akceptuje dowolny element reactowy, który będzie wyświetlany
w trakcie oczekiwania na załadowanie komponentu. Możesz umieścić komponent Suspense
w dowolnym miejscu nad “leniwym” (ang. lazy) komponentem. Możesz nawet opakować kilka “leniwych” komponentów
w jeden komponent Suspense
.
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}
Granice błędów
Jeśli inny moduł nie wczyta się poprawnie (na przykład z powodu awarii sieci), spowoduje to błąd. Możesz go obsłużyć aby zapewnić użytkownikowi lepsze doświadczenie, a także aby określić sposób obsługi błędu za pomocą granicy błędu. Taką granicę błędu możesz umieścić w dowolnym miejscu ponad “leniwymi” komponentami i, na przykład, aby wyświetlić stan błędu, gdy połączenie z siecią zostanie zerwane.
import React, { Suspense } from 'react';
import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
const MyComponent = () => (
<div>
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</MyErrorBoundary>
</div>
);
Podział kodu na podstawie ścieżki URL
Decyzja o tym, w których miejscach podzielić kod aplikacji, może okazać się kłopotliwa. Zależy ci na miejscach, że wybierasz miejsca, które równomiernie podzielą twoje pakiety, ale nie chcesz zepsuć doświadczeń użytkownika.
Dobrym punktem startowym są ścieżki (ang. routes) w aplikacji. Większość ludzi korzystających z Internetu przyzwyczajona jest, że przejście pomiędzy stronami zajmuje trochę czasu. Dodatkowo, zazwyczaj podczas takiego przejścia spora część ekranu jest renderowana ponownie. Można więc założyć, że użytkownik nie będzie wykonywał żadnych akcji na interfejsie podczas ładowania.
Oto przykład skonfigurowania dzielenia kodu aplikacji opartego na ścieżkach, przy użyciu biblioteki React Router wraz z funkcją React.lazy
.
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Wczytywanie...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
Eksporty nazwane
React.lazy
obecnie obsługuje tylko domyślne eksporty. Jeśli moduł, który chcesz zaimportować,
używa nazwanych eksportów, możesz utworzyć moduł pośredni, który ponownie eksportuje je jako
domyślny eksport. Gwarantuje to działanie mechanizmu “tree-shaking” (pol. potrząsanie drzewem), a także zapobiega pobieraniu nieużywanych komponentów.
// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));