Optymalizacja ładowania JavaScript
Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →
Parsowanie i uruchamianie kodu JavaScript wymaga pamięci i czasu. Dlatego wraz ze wzrostem aplikacji warto opóźniać ładowanie kodu do momentu pierwszego zapotrzebowania. React Native zawiera domyślnie włączone optymalizacje, a dodatkowo możesz zastosować techniki w swoim kodzie, które pomogą React efektywniej ładować aplikację. Istnieją też zaawansowane automatyczne optymalizacje (z własnymi kompromisami) odpowiednie dla bardzo dużych aplikacji.
Zalecane: Używaj Hermesa
Hermes to domyślny silnik dla nowych aplikacji React Native, wyspecjalizowany w efektywnym ładowaniu kodu. W wersjach produkcyjnych kod JavaScript jest w pełni kompilowany do bytecode'u z wyprzedzeniem. Bytecode jest ładowany do pamięci na żądanie i nie wymaga parsowania jak zwykły JavaScript.
Więcej o używaniu Hermesa w React Native przeczytasz tutaj.
Zalecane: Leniwie ładuj duże komponenty
Jeśli komponent z dużą ilością kodu/zależności prawdopodobnie nie będzie używany podczas początkowego renderowania aplikacji, możesz użyć Reactowego API lazy by opóźnić ładowanie jego kodu do pierwszego renderowania. Zazwyczaj warto rozważyć leniwe ładowanie komponentów na poziomie ekranów, aby dodawanie nowych ekranów nie zwiększało czasu uruchamiania aplikacji.
Więcej o leniwym ładowaniu komponentów z Suspense, w tym przykłady kodu, znajdziesz w dokumentacji Reacta.
Wskazówka: Unikaj efektów ubocznych modułów
Leniwe ładowanie komponentów może zmienić zachowanie aplikacji, jeśli moduły komponentów (lub ich zależności) mają efekty uboczne, jak modyfikowanie zmiennych globalnych czy subskrybowanie zdarzeń poza komponentem. Większość modułów w aplikacjach React nie powinna mieć efektów ubocznych.
import Logger from './utils/Logger';
// 🚩 🚩 🚩 Side effect! This must be executed before React can even begin to
// render the SplashScreen component, and can unexpectedly break code elsewhere
// in your app if you later decide to lazy-load SplashScreen.
global.logger = new Logger();
export function SplashScreen() {
// ...
}
Zaawansowane: Wywołuj require w miejscu
Czasami możesz chcieć opóźnić ładowanie kodu do pierwszego użycia bez korzystania z lazy lub asynchronicznego import(). Możesz to osiągnąć używając funkcji require() w miejscu, gdzie normalnie użyłbyś statycznego import na początku pliku.
import {Component} from 'react';
import {Text} from 'react-native';
// ... import some very expensive modules
export default function VeryExpensive() {
// ... lots and lots of rendering logic
return <Text>Very Expensive Component</Text>;
}
import {useCallback, useState} from 'react';
import {TouchableOpacity, View, Text} from 'react-native';
// Usually we would write a static import:
// import VeryExpensive from './VeryExpensive';
let VeryExpensive = null;
export default function Optimize() {
const [needsExpensive, setNeedsExpensive] = useState(false);
const didPress = useCallback(() => {
if (VeryExpensive == null) {
VeryExpensive = require('./VeryExpensive').default;
}
setNeedsExpensive(true);
}, []);
return (
<View style={{marginTop: 20}}>
<TouchableOpacity onPress={didPress}>
<Text>Load</Text>
</TouchableOpacity>
{needsExpensive ? <VeryExpensive /> : null}
</View>
);
}
Zaawansowane: Automatyczne wstawianie wywołań require
Jeśli używasz React Native CLI do budowania aplikacji, wywołania require (ale nie import) będą automatycznie wstawiane zarówno w twoim kodzie, jak i w zewnętrznych pakietach (node_modules).
import {useCallback, useState} from 'react';
import {TouchableOpacity, View, Text} from 'react-native';
// This top-level require call will be evaluated lazily as part of the component below.
const VeryExpensive = require('./VeryExpensive').default;
export default function Optimize() {
const [needsExpensive, setNeedsExpensive] = useState(false);
const didPress = useCallback(() => {
setNeedsExpensive(true);
}, []);
return (
<View style={{marginTop: 20}}>
<TouchableOpacity onPress={didPress}>
<Text>Load</Text>
</TouchableOpacity>
{needsExpensive ? <VeryExpensive /> : null}
</View>
);
}
Niektóre frameworki React Native wyłączają to zachowanie. W szczególności w projektach Expo wywołania require domyślnie nie są wstawiane. Możesz włączyć tę optymalizację edytując konfigurację Metro projektu i ustawiając inlineRequires: true w getTransformOptions.
Pułapki wstawiania require
Wstawianie wywołań require zmienia kolejność ewaluacji modułów i może nawet spowodować, że niektóre moduły nigdy nie zostaną ewaluowane. Zwykle jest to bezpieczne dzięki temu, że moduły JavaScript często pisane są bez efektów ubocznych.
Jeśli któryś z twoich modułów ma efekty uboczne - np. inicjalizuje mechanizm logowania czy modyfikuje globalne API używane przez resztę kodu - możesz doświadczyć nieoczekiwanego zachowania lub nawet błędów. W takich przypadkach warto wykluczyć konkretne moduły z optymalizacji lub całkowicie ją wyłączyć.
Aby wyłączyć wszystkie automatyczne wstawianie wywołań require:
Zaktualizuj metro.config.js ustawiając opcję transformera inlineRequires na false:
module.exports = {
transformer: {
async getTransformOptions() {
return {
transform: {
inlineRequires: false,
},
};
},
},
};
Aby wykluczyć tylko niektóre moduły z wstawiania require:
Istnieją dwie istotne opcje transformera: inlineRequires.blockList i nonInlinedRequires. W przykładzie kodu zobaczysz jak użyć każdej z nich.
module.exports = {
transformer: {
async getTransformOptions() {
return {
transform: {
inlineRequires: {
blockList: {
// require() calls in `DoNotInlineHere.js` will not be inlined.
[require.resolve('./src/DoNotInlineHere.js')]: true,
// require() calls anywhere else will be inlined, unless they
// match any entry nonInlinedRequires (see below).
},
},
nonInlinedRequires: [
// require('react') calls will not be inlined anywhere
'react',
],
},
};
},
},
};
Więcej szczegółów na temat konfiguracji i precyzyjnego dostosowywania wbudowanych wywołań require znajdziesz w dokumentacji getTransformOptions w Metro.
Zaawansowane: Korzystanie z pakietów modułów z dostępem swobodnym (bez Hermesa)
Niewspierane przy używaniu Hermesa. Bytecode Hermesa nie jest kompatybilny z formatem pakietów RAM i zapewnia porównywalną (lub lepszą) wydajność we wszystkich przypadkach użycia.
Pakiety modułów z dostępem swobodnym (znane też jako pakiety RAM) współdziałają z technikami opisanymi powyżej, aby ograniczyć ilość kodu JavaScript wymagającą parsowania i wczytania do pamięci. Każdy moduł przechowywany jest jako osobny ciąg znaków (lub plik), który jest parsowany dopiero w momencie konieczności wykonania modułu.
Pakiety RAM mogą być fizycznie podzielone na osobne pliki lub wykorzystywać format indeksowany, składający się z tabeli przeglądowej wielu modułów w jednym pliku.
- Android
- iOS
On Android enable the RAM format by editing your android/app/build.gradle file. Before the line apply from: "../../node_modules/react-native/react.gradle" add or amend the project.ext.react block:
project.ext.react = [
bundleCommand: "ram-bundle",
]
Use the following lines on Android if you want to use a single indexed file:
project.ext.react = [
bundleCommand: "ram-bundle",
extraPackagerArgs: ["--indexed-ram-bundle"]
]
On iOS, RAM bundles are always indexed ( = single file).
Enable the RAM format in Xcode by editing the build phase "Bundle React Native code and images". Before ../node_modules/react-native/scripts/react-native-xcode.sh add export BUNDLE_COMMAND="ram-bundle":
export BUNDLE_COMMAND="ram-bundle"
export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh
Szczegółowe informacje o konfiguracji i dostosowywaniu budowania pakietów RAM znajdziesz w dokumentacji getTransformOptions w Metro.