Wie verzögertes Rendern inaktiver Tabs die Ladezeit senkt und welcher Trade-off dabei entsteht
Eine Bottom-Tab-Navigation mit fünf oder sechs Tabs rendert ohne bewusste Konfiguration mitunter alle enthaltenen Screens bereits beim allerersten App-Start, obwohl die Nutzerin zunächst nur den ersten Tab überhaupt zu Gesicht bekommt. Lazy Loading verschiebt das Rendern eines Tabs auf den Moment, in dem er tatsächlich zum ersten Mal aktiv wird, und kann die wahrgenommene Startzeit einer Tab-App spürbar verkürzen. Der Effekt hat allerdings einen Preis, wenn ein bereits besuchter Tab beim Wechsel seinen internen Zustand verliert. Dieser Artikel zeigt die praktische Konfiguration, den konkreten Trade-off und wie sich der Effekt tatsächlich messen lässt.
Inhaltsverzeichnis
- 1. Was Lazy Loading bei Tab-Navigatoren konkret bedeutet
- 2. Standardverhalten versus explizite Konfiguration
- 3. Der Trade-off: Zustandsverlust beim Wechsel zwischen Tabs
- 4. freezeOnBlur und detachInactiveScreens im Detail
- 5. Praktische Konfiguration im Code
- 6. Lazy-Verhalten der Tabs-Komponente in Expo Router
- 7. Speicherverbrauch gegen Ladezeit abwägen
- 8. Den Effekt tatsächlich messen statt nur zu vermuten
- 9. Kombination mit Suspense und Datenladen pro Tab
- 10. Zusammenfassung
- 11. FAQ
1. Was Lazy Loading bei Tab-Navigatoren konkret bedeutet
Ohne Lazy Loading initialisiert ein Bottom-Tab-Navigator standardmäßig alle konfigurierten Tab-Screens gleichzeitig beim ersten Rendern des Navigators, selbst wenn nur ein einziger Tab tatsächlich sichtbar ist. Jeder dieser Screens durchläuft dabei den vollständigen React-Lifecycle inklusive aller enthaltenen useEffect-Hooks, wodurch beispielsweise ein Datenladevorgang im vierten Tab bereits ausgeführt wird, obwohl die Nutzerin diesen Tab möglicherweise nie öffnet.
Mit aktiviertem Lazy Loading wird ein Tab-Screen erst dann tatsächlich gemountet, wenn er zum ersten Mal aktiv ausgewählt wird, wodurch sich die Menge an Arbeit beim initialen App-Start auf genau den Tab reduziert, der tatsächlich sichtbar ist. Bereits besuchte Tabs bleiben nach dem ersten Mounten grundsätzlich im Speicher erhalten, sodass ein erneuter Wechsel zu einem zuvor bereits geöffneten Tab kein erneutes Mounten auslöst, sondern lediglich die Sichtbarkeit ändert.
2. Standardverhalten versus explizite Konfiguration
In neueren Versionen von @react-navigation/bottom-tabs ist Lazy Loading bereits standardmäßig aktiviert, was in vielen bestehenden Projekten dazu führt, dass Entwicklerinnen und Entwickler den Effekt gar nicht bewusst konfiguriert haben, sondern ihn implizit bereits nutzen. Dennoch lohnt sich eine explizite Konfiguration über die lazy-Option am jeweiligen Tab.Navigator, weil dadurch im Code sichtbar dokumentiert wird, dass dieses Verhalten bewusst gewählt und nicht zufällig vorhanden ist.
Für einzelne Tabs, bei denen ein sofortiges Vorladen tatsächlich gewünscht ist, etwa ein Home-Tab mit Daten, die ohnehin unmittelbar nach dem Öffnen der App benötigt werden, lässt sich Lazy Loading gezielt über die lazy-Option auf Ebene des einzelnen Screens deaktivieren, während es für alle übrigen Tabs aktiv bleibt. Diese gemischte Konfiguration ist in der Praxis häufig sinnvoller als eine pauschale Ein- oder Ausschaltung für den gesamten Navigator.
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
export function MainTabs() {
return (
<Tab.Navigator screenOptions={{ lazy: true }}>
<Tab.Screen
name="Home"
component={HomeScreen}
options={{ lazy: false }} // bewusst sofort geladen
/>
<Tab.Screen name="Search" component={SearchScreen} />
<Tab.Screen name="Orders" component={OrdersScreen} />
<Tab.Screen name="Account" component={AccountScreen} />
</Tab.Navigator>
);
}
3. Der Trade-off: Zustandsverlust beim Wechsel zwischen Tabs
Lazy Loading betrifft ausschließlich den Zeitpunkt des ersten Mountens und nicht das Verhalten bei nachfolgenden Tab-Wechseln, was häufig zu einem Missverständnis führt: Standardmäßig bleibt ein einmal gemounteter Tab-Screen dauerhaft im Speicher, auch wenn er nicht sichtbar ist, wodurch sein interner Zustand wie eine Formulareingabe oder eine Scroll-Position beim Zurückwechseln erhalten bleibt. Wird zusätzlich detachInactiveScreens oder eine ähnliche Optimierung über react-native-screens aktiviert, werden inaktive Screens dagegen tatsächlich aus dem nativen View-Baum entfernt, was Speicher spart, aber gleichzeitig lokalen Komponentenzustand zerstören kann.
Dieser Unterschied ist entscheidend für die Entscheidung, welche Optimierung für welchen Tab sinnvoll ist: Ein Tab mit einer langen, unveränderlichen Liste profitiert von einer aggressiven Speicherbereinigung, weil ein Neuaufbau kaum spürbar ist, während ein Tab mit einem teilweise ausgefüllten Formular oder einer aufwendig gescrollten Position durch dieselbe Optimierung eine spürbar schlechtere Nutzererfahrung erzeugen kann, wenn die Eingaben beim erneuten Öffnen plötzlich verschwunden sind.
4. freezeOnBlur und detachInactiveScreens im Detail
react-native-screens stellt mit freezeOnBlur eine feingranularere Alternative zum vollständigen Entfernen eines Screens bereit: Statt den Screen komplett zu unmounten, werden lediglich alle Re-Renders und Effekte pausiert, während der native View weiterhin im Speicher und im View-Baum vorhanden bleibt. Dadurch bleibt der komplette React-Zustand, einschließlich Formulareingaben und Scroll-Position, vollständig erhalten, während gleichzeitig unnötige Hintergrund-Renderarbeit vermieden wird, was in den meisten Fällen den besten Kompromiss zwischen Speicherverbrauch und Zustandserhalt darstellt.
Die aggressivere Option detachInactiveScreens entfernt inaktive Screens dagegen vollständig aus dem nativen View-Baum, was den Speicherverbrauch bei Apps mit sehr vielen Tabs oder sehr speicherintensiven Screens spürbar senkt, aber wie beschrieben jeglichen lokalen Zustand verwirft. Ein sinnvoller Mittelweg für die meisten Produktions-Apps besteht darin, freezeOnBlur global zu aktivieren und detachInactiveScreens nur gezielt für einzelne, bekanntermaßen speicherhungrige Tabs einzuschalten, statt es pauschal für die gesamte Navigation zu setzen.
5. Praktische Konfiguration im Code
Die Konfiguration von freezeOnBlur erfolgt über die screenOptions des Tab-Navigators und wirkt für alle enthaltenen Screens einheitlich, sofern kein einzelner Screen dies explizit überschreibt. Für gezieltes detachInactiveScreens auf Ebene eines einzelnen Stack-Navigators innerhalb eines Tabs lässt sich die Option direkt an diesem verschachtelten Navigator setzen, sodass nur der jeweilige Bereich betroffen ist und der Rest der App unverändert bleibt.
In der Praxis empfiehlt es sich, diese Konfiguration an einer zentralen Stelle im Navigations-Setup zu dokumentieren, etwa mit einem Kommentar, der begründet, warum ein bestimmter Tab von der Standardkonfiguration abweicht, damit spätere Änderungen am Navigationsbaum nicht versehentlich eine bewusst getroffene Performance-Entscheidung rückgängig machen.
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { enableFreeze, enableScreens } from 'react-native-screens';
enableScreens();
enableFreeze(true); // globale Voreinstellung für alle Screens
const Tab = createBottomTabNavigator();
export function MainTabs() {
return (
<Tab.Navigator screenOptions={{ lazy: true, freezeOnBlur: true }}>
<Tab.Screen name="Home" component={HomeScreen} options={{ lazy: false }} />
<Tab.Screen name="Feed" component={FeedScreen} />
{/* Feed enthaelt viele Bilder, hier lohnt sich gezieltes Detach */}
<Tab.Screen
name="Media"
component={MediaStack}
options={{ freezeOnBlur: false, detachInactiveScreens: true }}
/>
</Tab.Navigator>
);
}
6. Lazy-Verhalten der Tabs-Komponente in Expo Router
Die Tabs-Komponente von Expo Router baut intern auf demselben Bottom-Tab-Navigator von React Navigation auf und übernimmt dessen Lazy-Loading-Verhalten größtenteils unverändert, wodurch sich dieselben Optionen wie lazy und freezeOnBlur auch dort über screenOptions setzen lassen. Der Unterschied liegt hauptsächlich darin, dass Expo Router die Tab-Screens automatisch aus der Ordnerstruktur im app/(tabs)-Verzeichnis ableitet, statt sie manuell als Tab.Screen-Elemente aufzulisten, was die Konfiguration syntaktisch etwas anders aussehen lässt, ohne das zugrunde liegende Verhalten zu verändern.
Weil Expo Router zusätzlich URL-basiertes Routing mit der Tab-Navigation kombiniert, ist besonders relevant, dass ein direkter Deep Link zu einem tief liegenden Tab-Screen dessen Lazy-Loading-Verhalten umgeht, weil der Ziel-Tab in diesem Fall sofort gemountet werden muss, um den Link korrekt darzustellen. Dieses Verhalten ist beabsichtigt, sollte bei der Performance-Planung aber berücksichtigt werden, weil ein per Deep Link geöffneter Tab nicht von der verzögerten Initialisierung profitiert.
7. Speicherverbrauch gegen Ladezeit abwägen
Bei einer App mit nur drei oder vier eher einfachen Tabs bringt eine aggressive Speicheroptimierung durch detachInactiveScreens häufig kaum spürbaren Nutzen, weil der zusätzliche Speicherverbrauch aller gemounteten Tabs ohnehin gering bleibt, während der Trade-off in Form von verlorenem Zustand bei jedem Tab-Wechsel spürbar negativ auffällt. Bei einer App mit acht oder mehr Tabs, von denen einige aufwendige Listen, Karten oder Medien-Grids enthalten, kehrt sich diese Abwägung häufig um, weil der Speicherverbrauch aller gleichzeitig aktiven Screens tatsächlich zu spürbaren Rucklern oder sogar App-Abstürzen auf älteren Geräten führen kann.
Als praktische Faustregel eignet sich eine Kategorisierung der eigenen Tabs in drei Gruppen: Tabs mit unveränderlichem, leicht neu ladbarem Inhalt profitieren von aggressivem Detachment, Tabs mit wichtigem, schwer wiederherstellbarem lokalem Zustand sollten freezeOnBlur statt Detachment nutzen, und ein einzelner, besonders häufig genutzter Startbildschirm-Tab sollte in der Regel ganz von Lazy Loading ausgenommen werden.
8. Den Effekt tatsächlich messen statt nur zu vermuten
Um den Effekt von Lazy Loading objektiv zu belegen, eignet sich ein einfacher Vergleich der Time-to-Interactive-Metrik über das React Native Performance Monitor Overlay oder ein dediziertes Tool wie react-native-performance, gemessen jeweils einmal mit vollständig deaktiviertem Lazy Loading und einmal mit der Standardkonfiguration. Besonders aussagekräftig ist eine Messung auf einem tatsächlich älteren Mittelklasse-Android-Gerät, weil der Unterschied auf einem aktuellen High-End-Gerät durch dessen hohe Rohleistung häufig kaum wahrnehmbar ist, während er auf schwächerer Hardware deutlich sichtbar wird.
Zusätzlich zur reinen Ladezeit lohnt sich eine Beobachtung der Speicherbelegung über die nativen Profiling-Werkzeuge von Android Studio beziehungsweise Xcode Instruments, um zu prüfen, ob eine gewählte Kombination aus lazy, freezeOnBlur und detachInactiveScreens tatsächlich den erwarteten Effekt erzielt, statt sich ausschließlich auf die Dokumentation der jeweiligen Bibliothek zu verlassen, deren Verhalten sich zwischen Versionen durchaus ändern kann.
9. Kombination mit Suspense und Datenladen pro Tab
Lazy Loading auf Navigator-Ebene und Datenladen innerhalb eines Screens lassen sich sinnvoll kombinieren, indem ein Tab-Screen erst beim eigenen Mounten, ausgelöst durch das erstmalige Aktivieren des Tabs, seinen zugehörigen Datenabruf startet, statt Daten für alle Tabs bereits beim App-Start vorzuladen. In Kombination mit React Suspense und einer entsprechenden Datenbibliothek lässt sich dieser Ladevorgang zusätzlich mit einem Skeleton-Screen visuell abfedern, sodass der ohnehin unvermeidbare erste Ladevorgang eines Tabs nicht als spürbare Verzögerung wahrgenommen wird.
Wichtig ist dabei, den Datenabruf nicht bei jedem erneuten Sichtbarwerden eines bereits gemounteten Tabs zu wiederholen, sondern ihn über einen geeigneten Cache-Mechanismus, etwa React Query oder SWR, an das erste Mounten zu binden und bei nachfolgenden Tab-Wechseln lediglich die zwischengespeicherten Daten erneut anzuzeigen, solange sie nicht als veraltet markiert wurden.
| Option | Effekt | Zustand nach Tab-Wechsel | Empfohlen für |
|---|---|---|---|
| lazy: true | Screen mountet erst bei erster Aktivierung | Bleibt nach erstem Mounten erhalten | Alle Tabs außer dem Startbildschirm |
| lazy: false | Screen mountet sofort beim App-Start | Immer erhalten | Häufig genutzter Home-Tab |
| freezeOnBlur: true | Re-Renders und Effekte pausiert, View bleibt bestehen | Vollständig erhalten | Tabs mit Formularen oder wichtiger Scroll-Position |
| detachInactiveScreens: true | Screen komplett aus dem View-Baum entfernt | Wird verworfen | Speicherintensive Tabs mit unveränderlichem Inhalt |
| Suspense + Cache pro Tab | Datenabruf an erstes Mounten gebunden | Zwischengespeicherte Daten bleiben sichtbar | Tabs mit eigenem Datenladevorgang |
Mironsoft
React-Native-App-Entwicklung und Magento-Anbindung
Eine mobile App zum Magento-Shop, die wirklich rund läuft?
Wir entwickeln React-Native-Apps, die sauber an die Magento REST- oder GraphQL-API angebunden sind, von der ersten Codezeile bis zur Veröffentlichung im App Store und bei Google Play.
App-Konzeption
Architektur und Feature-Umfang einer Magento-angebundenen App gemeinsam planen.
Magento-API-Integration
Produktkatalog, Warenkorb und Checkout sauber an die Shop-API anbinden.
Store-Veröffentlichung
App Store- und Google-Play-Freigabeprozess ohne Stolperfallen begleiten.
10. Zusammenfassung
Tab-Navigation-Performance: Das Wichtigste auf einen Blick
Effekt
Lazy Loading verschiebt das Mounten eines Tabs auf den Moment seiner ersten Aktivierung und senkt so die initiale Ladezeit.
Trade-off
Aggressive Speicheroptimierung wie detachInactiveScreens verwirft lokalen Zustand beim Tab-Wechsel.
Mittelweg
freezeOnBlur pausiert Re-Renders ohne den Zustand zu verlieren und ist meist der beste Kompromiss.
Messung
Der Effekt zeigt sich vor allem auf älteren Mittelklasse-Geräten, weniger auf aktuellen High-End-Geräten.