imperativ komponieren statt zufällig verschachteln
React Navigation gibt Entwicklerteams die volle Kontrolle über den Navigationsbaum einer React-Native-App: createNativeStackNavigator, createBottomTabNavigator und createDrawerNavigator lassen sich gezielt ineinander verschachteln, mit TypeScript typisieren und über eine Linking-Konfiguration mit Deep Links verbinden, ganz ohne dateibasierte Routing-Konventionen.
Inhaltsverzeichnis
- 1. Warum imperative Navigator-Komposition mit React Navigation zählt
- 2. Stack Navigator: createNativeStackNavigator als Fundament
- 3. Tab Navigator: createBottomTabNavigator für primäre Navigation
- 4. Drawer Navigator und die Verschachtelung von Stacks in Tabs in Drawer
- 5. Typisierte Navigation: RootStackParamList mit TypeScript
- 6. navigate() vs. push() vs. replace(): Navigationsverhalten steuern
- 7. screenOptions und Header-Customization
- 8. Linking-Konfiguration für Deep Links
- 9. React Navigation im Vergleich: Stack, Tab, Drawer und Expo Router
- 10. Zusammenfassung
- 11. FAQ
1. Warum imperative Navigator-Komposition mit React Navigation zählt
React Navigation ist die am weitesten verbreitete Navigationslösung für React Native und arbeitet nach einem imperativen Kompositionsmodell: Man erstellt Navigatoren explizit über Fabrikfunktionen wie createNativeStackNavigator, createBottomTabNavigator und createDrawerNavigator und verschachtelt sie gezielt in JSX. Das steht im Gegensatz zu dateibasierten Routing-Konventionen, bei denen die Ordnerstruktur selbst die Navigation definiert. Der Vorteil der imperativen Herangehensweise: Der Navigationsbaum ist an keiner Stelle implizit, jede Verschachtelungsebene steht sichtbar im Code und lässt sich exakt an die Informationsarchitektur der App anpassen.
Dieser Artikel behandelt bewusst nicht die dateibasierte app/-Verzeichniskonvention von Expo Router, das ist ein eigenes Thema. Stattdessen geht es um React Navigation als direkt genutzte Bibliothek: für Teams, die eine bare React-Native-App ohne Expo betreiben, oder für Projekte mit hochgradig individuellen Navigationszustandsmaschinen, bei denen die volle Kontrolle über Navigator-Erstellung und -Verschachtelung unverzichtbar ist. Wichtig zu wissen: Auch Expo Router baut intern auf React Navigation auf, wer die Verschachtelungsregeln von Stack, Tab und Drawer versteht, versteht damit auch, was unter der Haube von Expo Router passiert.
In den folgenden Abschnitten wird gezeigt, wie man mit React Navigation Stacks in Tabs und Tabs in einem Drawer verschachtelt, wie man Navigation mit TypeScript typsicher macht, welchen Unterschied navigate, push und replace tatsächlich machen, und wie eine Linking-Konfiguration Deep Links exakt auf die verschachtelte Navigatorstruktur abbildet.
2. Stack Navigator: createNativeStackNavigator als Fundament
Der Stack Navigator ist der Grundbaustein linearer Navigationsflüsse. createNativeStackNavigator aus dem Paket @react-navigation/native-stack nutzt auf iOS UINavigationController und auf Android Fragment-Transitions, wodurch Übergänge nativ performant ablaufen statt über JavaScript-getriebene Animationen. Ein React Navigation-Stack besteht aus einer Stack.Navigator-Komponente mit mehreren Stack.Screen-Kindern, jedes mit eindeutigem name und zugehöriger component. Neue Screens werden oben auf den Stack gelegt, die Zurück-Geste oder der Zurück-Button entfernt den obersten Eintrag wieder.
Typische Stacks in einer Commerce-App sind Produktliste zu Produktdetail zu Warenkorb zu Checkout, ein klar linearer Weg, bei dem der Nutzer jederzeit weiß, wie er zurückkommt. initialRouteName legt den Startbildschirm eines Stacks fest, unabhängig von der Reihenfolge der Stack.Screen-Deklarationen. Ein einzelner globaler Stack für die gesamte App führt allerdings schnell zu Problemen, sobald parallele Bereiche wie Home, Warenkorb und Konto gleichzeitig sichtbar sein sollen, genau dafür kombiniert man den Stack mit einem Tab Navigator, wie im nächsten Abschnitt beschrieben.
# Install React Navigation core and navigator packages
npm install @react-navigation/native @react-navigation/native-stack
npm install @react-navigation/bottom-tabs @react-navigation/drawer
# Required native dependencies (bare React Native, not needed with Expo managed workflow)
npm install react-native-screens react-native-safe-area-context
npm install react-native-gesture-handler react-native-reanimated
# iOS only: link native modules via CocoaPods
cd ios && pod install && cd ..
3. Tab Navigator: createBottomTabNavigator für primäre Navigation
createBottomTabNavigator aus @react-navigation/bottom-tabs bildet die primäre Navigationsebene ab, meist als untere Tab-Leiste mit drei bis fünf Einträgen wie Home, Suche, Warenkorb und Konto. Jeder Tab.Screen bekommt über tabBarIcon eine Funktion, die abhängig vom Fokus-Status ein anderes Icon rendert, und über tabBarBadge lässt sich beispielsweise die Anzahl der Artikel im Warenkorb direkt auf dem Tab-Icon anzeigen. Diese primäre Ebene in React Navigation unterscheidet sich fundamental vom Stack: Tabs stehen nebeneinander, nicht übereinander, und ein Tab-Wechsel ist kein Vorwärts- oder Rückwärtsschritt in der Historie.
Ein wichtiger Aspekt beim Einsatz von Tabs ist das Speicherverhalten: Standardmäßig bleiben alle Tab-Screens im Speicher gemountet, sobald sie einmal besucht wurden, damit der Wechsel zwischen Tabs sofort und ohne Neu-Rendering erfolgt. Bei komplexeren Tabs mit eigenem Datenstand kann das über die Navigator-Option unmountOnBlur pro Screen angepasst werden, was Speicher spart, aber den State beim erneuten Fokussieren zurücksetzt. Für die meisten Commerce-Apps überwiegt der Vorteil des dauerhaften Mountens, weil Scroll-Position und Formulareingaben erhalten bleiben, wenn der Nutzer zwischendurch in einen anderen Tab wechselt.
4. Drawer Navigator und die Verschachtelung von Stacks in Tabs in Drawer
createDrawerNavigator aus @react-navigation/drawer eignet sich für sekundäre, seltener genutzte Ziele wie Store-Auswahl, Support, Impressum oder Konto-Umschaltung, die per Seitenmenü statt permanenter Tab-Leiste erreichbar sein sollen. Die eigentliche Stärke von React Navigation zeigt sich erst, wenn man Drawer, Tabs und Stacks korrekt ineinander verschachtelt: Der Drawer bildet die äußerste Ebene, darin liegt der Tab Navigator, und jeder einzelne Tab bekommt wiederum seinen eigenen Stack Navigator. So bleibt die Tab-Leiste sichtbar, während innerhalb eines Tabs beliebig tief navigiert werden kann, ohne dass ein Push in einem Tab die Navigation in einem anderen Tab beeinflusst.
Die Reihenfolge der Verschachtelung ist keine Geschmacksfrage, sondern hat konkrete Konsequenzen: Würde man stattdessen Tabs außen und den Drawer nur um einen einzelnen Tab-Inhalt legen, verschwände die Tab-Leiste beim Öffnen des Drawers nicht mehr korrekt, und der Header würde auf der falschen Verschachtelungsebene erscheinen. Ein weiterer praktischer Punkt: Wenn sowohl der Drawer als auch ein verschachtelter Stack die Kante des Bildschirms für Wischgesten beanspruchen, entsteht ein Gestenkonflikt. In der Praxis deaktiviert man deshalb häufig die Stack-interne Zurück-Wischgeste auf dem obersten Screen jedes Tabs oder schränkt die Drawer-Öffnungsgeste auf einen schmalen Randbereich ein.
// App.js - compose Drawer > Tabs > Stacks
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createDrawerNavigator } from '@react-navigation/drawer';
const HomeStack = createNativeStackNavigator();
const CartStack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();
const Drawer = createDrawerNavigator();
// Each tab gets its own stack, so pushing a detail screen
// does not hide the tab bar and back navigation stays local to that tab
function HomeStackScreen() {
return (
<HomeStack.Navigator screenOptions={{ headerShown: true }}>
<HomeStack.Screen name="ProductList" component={ProductListScreen} />
<HomeStack.Screen name="ProductDetail" component={ProductDetailScreen} />
</HomeStack.Navigator>
);
}
function CartStackScreen() {
return (
<CartStack.Navigator>
<CartStack.Screen name="Cart" component={CartScreen} />
<CartStack.Screen name="Checkout" component={CheckoutScreen} />
</CartStack.Navigator>
);
}
function MainTabs() {
return (
<Tab.Navigator screenOptions={{ tabBarActiveTintColor: '#4338ca' }}>
<Tab.Screen name="HomeTab" component={HomeStackScreen} options={{ title: 'Home' }} />
<Tab.Screen name="CartTab" component={CartStackScreen} options={{ title: 'Cart', tabBarBadge: 3 }} />
<Tab.Screen name="Account" component={AccountScreen} />
</Tab.Navigator>
);
}
export default function App() {
return (
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="Main" component={MainTabs} options={{ headerShown: false }} />
<Drawer.Screen name="Support" component={SupportScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}
5. Typisierte Navigation: RootStackParamList mit TypeScript
Ohne Typisierung akzeptiert navigation.navigate('ProductDetail', { id: 42 }) jeden Routennamen und jedes Parameterobjekt, Tippfehler im Routennamen oder fehlende Parameter fallen erst zur Laufzeit auf. React Navigation löst das über ein zentrales RootStackParamList-Type, das jeden Routennamen auf seinen erwarteten Parametertyp abbildet, undefined für Screens ohne Parameter. Dieses Type wird sowohl beim useNavigation-Hook als generischer Parameter von NativeStackNavigationProp verwendet als auch bei useRoute über RouteProp, wodurch route.params in jedem Screen exakt typisiert zur Verfügung steht.
Der praktische Effekt: TypeScript meldet bereits beim Kompilieren einen Fehler, wenn ein Screen-Name falsch geschrieben ist oder ein Pflichtparameter fehlt, statt dass der Fehler erst bei einem realen Nutzerklick sichtbar wird. Gerade bei größeren Teams, die parallel an verschiedenen Screens arbeiten, verhindert diese Typisierung, dass sich Parameter-Verträge zwischen Screens unbemerkt auseinanderentwickeln. Da RootStackParamList in einer zentralen Datei gepflegt wird, wird jede Änderung an einem Parameter sofort an allen Aufrufstellen sichtbar, die den betroffenen Screen ansteuern.
// navigation/types.ts - central param list shared across the app
export type RootStackParamList = {
ProductList: undefined;
ProductDetail: { productId: string; sku: string };
Checkout: { cartId: string };
};
// ProductListScreen.tsx
import { useNavigation } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import type { RootStackParamList } from '../navigation/types';
type ProductListNavProp = NativeStackNavigationProp<RootStackParamList, 'ProductList'>;
function ProductListScreen() {
const navigation = useNavigation<ProductListNavProp>();
const openProduct = (productId, sku) => {
// TypeScript checks the route name and the shape of params at compile time
navigation.navigate('ProductDetail', { productId, sku });
};
return <ProductCard onPress={openProduct} />;
}
// ProductDetailScreen.tsx
import { useRoute } from '@react-navigation/native';
import type { RouteProp } from '@react-navigation/native';
import type { RootStackParamList } from '../navigation/types';
type ProductDetailRouteProp = RouteProp<RootStackParamList, 'ProductDetail'>;
function ProductDetailScreen() {
const route = useRoute<ProductDetailRouteProp>();
const { productId, sku } = route.params; // fully typed, no manual casting needed
return <ProductView productId={productId} sku={sku} />;
}
6. navigate() vs. push() vs. replace(): Navigationsverhalten steuern
Die drei Methoden navigate, push und replace wirken oberflächlich ähnlich, unterscheiden sich in React Navigation aber grundlegend im Umgang mit der Navigationshistorie. navigation.navigate('ProductList') springt zu einer bereits existierenden Instanz der Route im aktuellen Stack, falls vorhanden, statt eine neue oben aufzulegen, das ist ideal, um zu einem bekannten Bildschirm zurückzukehren, ohne die Historie unnötig wachsen zu lassen. navigation.push('ProductDetail', { productId }) hingegen legt immer eine neue Instanz auf den Stack, selbst wenn derselbe Routenname bereits vorhanden ist, was für verwandte Produkte wichtig ist, bei denen der Nutzer sich durch mehrere Detailseiten hindurchklicken und über jede einzelne zurückgehen können soll.
navigation.replace('OrderConfirmation', { orderId }) ersetzt den aktuellen Screen, ohne einen neuen Historieneintrag zu erzeugen, das verhindert zum Beispiel, dass ein Nutzer nach erfolgreichem Checkout per Zurück-Button erneut auf dem Zahlungsbildschirm landet. Ergänzend gibt es goBack() für einen einzelnen Schritt zurück und popToTop(), um den gesamten Stack bis zum ersten Screen zu leeren, etwa wenn ein Nutzer nach dem Logout wieder auf dem Startbildschirm ankommen soll. Die bewusste Wahl zwischen diesen Methoden entscheidet direkt darüber, wie sich die Zurück-Navigation für den Nutzer anfühlt.
// Difference between navigate, push and replace
function ProductDetailScreen({ navigation, route }) {
const openRelatedProduct = (relatedId) => {
// push always adds a new screen instance, even to the same route name,
// so users can drill through several related products and go back through each one
navigation.push('ProductDetail', { productId: relatedId });
};
const goToProductList = () => {
// navigate reuses an existing ProductList instance already in the stack
// instead of stacking a duplicate on top
navigation.navigate('ProductList');
};
const finishCheckout = () => {
// replace swaps the current screen so Checkout never appears
// again when the user presses the hardware back button
navigation.replace('OrderConfirmation', { orderId: '10293' });
};
return (
<ProductActions
onRelated={openRelatedProduct}
onBack={goToProductList}
onCheckout={finishCheckout}
/>
);
}
// Header customization via screenOptions
function HomeStackScreen() {
return (
<HomeStack.Navigator
screenOptions={{
headerStyle: { backgroundColor: '#1e1b4b' },
headerTintColor: '#ffffff',
headerBackTitleVisible: false,
}}
>
<HomeStack.Screen
name="ProductList"
component={ProductListScreen}
options={{
headerTitle: 'Storefront',
headerRight: () => <CartBadgeButton />,
}}
/>
</HomeStack.Navigator>
);
}
7. screenOptions und Header-Customization
Die Prop screenOptions am Navigator selbst definiert Standardeinstellungen für alle darin enthaltenen Screens, während die options-Prop einzelner Screen-Elemente diese Standards gezielt überschreibt. In React Navigation lässt sich damit zum Beispiel headerStyle und headerTintColor einmalig für den gesamten Stack im Corporate Design des Shops setzen, während einzelne Screens einen abweichenden Titel oder eigene Header-Buttons über headerRight definieren, etwa einen Warenkorb-Button mit Badge-Zähler direkt im Header der Produktliste.
Weitere häufig genutzte Optionen sind headerShown: false, um für verschachtelte Navigatoren doppelte Header zu vermeiden, headerBackTitleVisible: false, um auf iOS den Text neben dem Zurück-Pfeil auszublenden, und gestureEnabled, um die Zurück-Wischgeste pro Screen gezielt zu deaktivieren, etwa auf einem Zahlungsbildschirm, den der Nutzer nicht versehentlich per Wischgeste verlassen soll. Diese feingranulare Steuerung über screenOptions und options erlaubt es, Header-Verhalten konsistent für die ganze App festzulegen und trotzdem punktuell abzuweichen, ohne jeden Screen einzeln komplett neu konfigurieren zu müssen.
8. Linking-Konfiguration für Deep Links
Damit Push-Benachrichtigungen, Marketing-E-Mails oder universelle Links direkt auf den richtigen Screen innerhalb der verschachtelten Navigatorstruktur zeigen, benötigt React Navigation ein linking-Konfigurationsobjekt, das der NavigationContainer-Komponente übergeben wird. Es definiert prefixes für das Custom-URL-Schema der App und für universelle HTTPS-Links, sowie ein verschachteltes config.screens-Objekt, dessen Struktur exakt der Verschachtelung von Drawer, Tabs und Stacks im Navigationsbaum entsprechen muss.
Ein Link wie https://mironsoft.de/product/1029 muss also über dieselbe Pfadkette Drawer zu Tab zu Stack zu Screen aufgelöst werden, die auch beim manuellen Navigieren durchlaufen wird, sonst öffnet der Deep Link zwar den richtigen Screen, aber ohne die erwartete Tab-Leiste oder mit falsch wiederhergestelltem Navigationszustand. Für komplexere Fälle, in denen sich URL-Struktur und Navigator-Verschachtelung nicht direkt eins zu eins abbilden lassen, bietet React Navigation mit getStateFromPath einen Erweiterungspunkt, über den die Pfad-zu-State-Umwandlung individuell angepasst werden kann, etwa um Legacy-URLs aus einer bestehenden Magento-Storefront-Struktur auf die App-interne Navigation zu mappen.
{
"prefixes": ["mironshop://", "https://mironsoft.de"],
"config": {
"screens": {
"Main": {
"screens": {
"HomeTab": {
"screens": {
"ProductList": "products",
"ProductDetail": "product/:productId"
}
},
"CartTab": {
"screens": {
"Cart": "cart",
"Checkout": "checkout"
}
}
}
},
"Support": "support"
}
}
}
9. React Navigation im Vergleich: Stack, Tab, Drawer und Expo Router
Jede der drei Navigatorarten in React Navigation löst ein anderes Problem, und die Wahl der falschen Kombination führt zu spürbar schlechterer Nutzererfahrung. Die folgende Tabelle stellt die zentralen Unterschiede gegenüber, bevor im Anschluss geklärt wird, wann ein Team React Navigation direkt statt der dateibasierten Expo-Router-Konvention einsetzen sollte.
| Kriterium | Stack Navigator | Tab Navigator | Drawer Navigator |
|---|---|---|---|
| Einsatzzweck | Lineare Drill-down-Flüsse (Liste zu Detail zu Checkout) | Parallele Top-Level-Bereiche (Home, Warenkorb, Konto) | Seltene, sekundäre Ziele (Support, Store-Wechsel) |
| Speicherverhalten | Screens werden bei Push gemountet, bei Pop entfernt | Alle besuchten Tabs standardmäßig dauerhaft gemountet | Meist lazy, nur beim Öffnen gemountet |
| Typische Screen-Anzahl | 3 bis 15 Screens pro Stack | 3 bis 5 Tabs | 5 bis 10 Drawer-Einträge |
| Gestenunterstützung | Zurück-Wischgeste von der Bildschirmkante (iOS) | Optionales Wischen zwischen Tabs | Wischen von der Kante öffnet das Seitenmenü |
Ein Team sollte React Navigation direkt statt Expo Router einsetzen, wenn die App auf reinem React Native ohne Expo-Module läuft, etwa bei bestehenden Bare-Workflow-Projekten mit tiefen nativen Integrationen, die sich nicht ohne Weiteres in den Expo-Managed-Workflow überführen lassen. Ebenso sinnvoll ist der direkte Einsatz, wenn die App eine hochgradig individuelle Navigationszustandsmaschine benötigt, etwa dynamisch generierte Navigatorbäume basierend auf Backend-Konfiguration oder Custom-Navigatoren über createNavigatorFactory, die sich nicht in eine Ordnerkonvention pressen lassen. Für neue, einfach strukturierte Apps ohne solche Anforderungen ist Expo Router oft der schnellere Einstieg, aber das Verständnis der zugrunde liegenden React-Navigation-Mechanik bleibt in beiden Fällen unverzichtbar.
Mironsoft
React-Native-Apps und Mobile-Commerce-Storefronts auf Magento-Basis
Navigationsstruktur, die mit eurem Shop mitwächst?
Wir entwickeln React-Native-Apps, die per REST- oder GraphQL-API an Magento angebunden sind, inklusive sauber verschachtelter React-Navigation-Struktur aus Stack, Tab und Drawer, typisierter Navigation mit TypeScript und Deep-Link-Konfiguration für Push-Benachrichtigungen und Marketing-Kampagnen.
Navigations-Architektur
Verschachtelung von Stack, Tab und Drawer entlang eurer Shop-Informationsarchitektur planen
Code-Review
Bestehende React-Navigation-Implementierung auf Typisierung, Gesten und Deep Links prüfen
Magento-Anbindung
Produktkatalog, Checkout und Kundenkonto per REST/GraphQL in die App-Navigation integrieren
10. Zusammenfassung
Die zentrale Erkenntnis rund um React Navigation: Der Navigationsbaum einer React-Native-App entsteht nicht zufällig, sondern durch bewusste, imperative Komposition von Stack-, Tab- und Drawer-Navigatoren. Ein Drawer als äußerste Ebene, darin ein Tab Navigator, darin pro Tab ein eigener Stack, das ist das robusteste Muster für Commerce-Apps mit paralleler Top-Level-Navigation und tiefen Drill-down-Flüssen innerhalb einzelner Bereiche. Typisierte Navigation über RootStackParamList verhindert, dass Tippfehler in Routennamen oder fehlende Parameter erst beim Testen auf einem echten Gerät auffallen.
Der Unterschied zwischen navigate, push und replace entscheidet direkt über das Zurück-Verhalten aus Nutzersicht, und eine Linking-Konfiguration, die exakt der Navigator-Verschachtelung folgt, macht Deep Links aus Push-Benachrichtigungen und Marketing-E-Mails zuverlässig nutzbar. Wer eine bare React-Native-App ohne Expo betreibt oder eine hochgradig individuelle Navigationszustandsmaschine benötigt, profitiert vom direkten, expliziten Einsatz von React Navigation gegenüber dateibasierten Konventionen.
React Navigation: Stacks, Tabs und Drawer, das Wichtigste auf einen Blick
Verschachtelung
Drawer außen, Tab Navigator darin, pro Tab ein eigener Stack. Diese Reihenfolge hält Header und Gesten auf der richtigen Ebene.
Typisierung
RootStackParamList mit NativeStackNavigationProp und RouteProp verknüpfen, Tippfehler und fehlende Parameter fallen beim Kompilieren auf.
navigate vs. push vs. replace
navigate springt zu bestehender Instanz, push legt immer neu auf, replace ersetzt ohne Historieneintrag. Bewusst wählen.
Deep Links
Die linking-Konfiguration muss die Navigator-Verschachtelung exakt spiegeln, sonst fehlt Tab-Leiste oder Navigationszustand beim Öffnen.