
Möchten Sie eine React Native App mit mehreren Bildschirmen und einer komplizierten Navigationsstruktur erstellen?
Dieses Tutorial zeigt Entwicklern , wie man eine React Native App mit geschichteten Bildschirmen erstellt, Drawer-Navigationen mit anderen Navigatoren verschachtelt und plattformspezifische Navigation handhabt. Sie lernen die fortgeschrittenen Konzepte kennen, die Sie möglicherweise benötigen, wenn Sie eine professionelle App für die Produktion erstellen.
Die React Native-Dokumentation empfiehlt , React Native mit einem Framework zu verwenden. Dieses Tutorial verwendet Expo. Möchten Sie dieses Tutorial in Aktion sehen? Schauen Sie es sich auf YouTube an.
Hier ist ein Diagramm, das zeigt, was Sie erstellen werden.

In diesem Diagramm stellen durchgezogene Linien Navigatoren dar (Tabs, Drawer und Stack) und gepunktete Linien stellen die verschiedenen Bildschirme dar.
Ihr Root-Navigator wird ein Stack-Navigator sein. Er verwaltet den Ladebildschirm (Splash Screen) während des App-Starts. Er umfasst die Haupt- und Authentifizierungsbildschirme. Er enthält auch einen nur für das Web verfügbaren „Nicht gefunden“-Bildschirm.
Die Authentifizierungsbildschirme umfassen einen Bildschirm zum Anmelden, einen Bildschirm zum Zurücksetzen des Passworts und einen Bildschirm zur Registrierung.
Nach dem Anmelden haben Sie Zugriff auf die Hauptbildschirme, nämlich den Startbildschirm, den Optionsbildschirm, den Detailbildschirm und den Einstellungsbildschirm.
Unter Android greift man normalerweise über ein Burger-Menü auf die Einstellungen zu, während es unter iOS üblicher ist, ein Tab-Layout zu verwenden. Sie werden das spezifische Design der jeweiligen Plattform dynamisch nutzen. Zusätzlich werden Sie innerhalb des Home-Stacks den Optionsbildschirm mithilfe einer modalen Transition anzeigen.
Die App wird 8 Bildschirme umfassen, darunter den Startbildschirm. Sie nutzt ein Standard-Layout mit einer Elementliste auf dem Startbildschirm. Diese Elemente können Sie über den Optionsbildschirm filtern. Einzelne Elemente lassen sich auf dem Detailbildschirm anzeigen. Hinweis: Dieses Tutorial wird nicht das Hinzufügen der Listenoberfläche beinhalten.
Wenn Sie mitprogrammieren möchten, führt Sie dieser Abschnitt Schritt für Schritt durch die Projekteinrichtung. (Am Ende dieses Artikels finden Sie einen Abschnitt zur Fehlerbehebung, der häufige Fehler behandelt, die beim ersten Ausführen von Expo auftreten können.)
Erstellen Sie ein neues Projekt.
npx create-expo-app@latest
Geben Sie ihm einen passenden Namen.
✔ **What is your app named?** … complex-navigation-expoInstallieren Sie die Abhängigkeiten für den Drawer.
npx expo install @react-navigation/drawer react-native-gesture-handler react-native-reanimatedStarten Sie den Simulator.
npm run ios
Wenn Sie den Expo Router zu einer bestehenden App hinzufügen möchten, sehen Sie sich die Dokumentation an.
Löschen Sie alle Dateien in app/ außer app/_layout.tsx, app/+html.tsx und app/+not-found.tsx und benennen Sie den app/(tabs)/ Ordner um in app/(main)/.
Ändern Sie den Inhalt Ihres Root-Layouts in app/_layout.tsx wie folgt:
import {
DarkTheme,
DefaultTheme,
ThemeProvider,
} from "@react-navigation/native";
import { useFonts } from "expo-font";
import { Stack } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
import "react-native-reanimated";
import { useColorScheme } from "@/hooks/useColorScheme";
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const colorScheme = useColorScheme();
const [loaded] = useFonts({
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
});
useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
}
}, [loaded]);
if (!loaded) {
return null;
}
return (
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultIndie">
<Stack>
<Stack.Screen name="(main)" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" />
</Stack>
</ThemeProvider>
);
}
Damit die Bildschirme ansprechend aussehen, können Sie React Native Elements verwenden. Installieren Sie das React Native Elements Paket.
npm install @rneui/themed @rneui/base
Fügen Sie den ThemeProvider von React Native Elements zu Ihrem Root-Layout hinzu.
import {
DarkTheme,
DefaultTheme,
ThemeProvider,
} from "@react-navigation/native";
// 👇
import { Platform } from "react-native";
import {
lightColors,
createTheme,
ThemeProvider as RNEThemeProvider,
} from "@rneui/themed";
// ☝️
import { useFonts } from "expo-font";
import { Stack } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
import "react-native-reanimated";
import { useColorScheme } from "@/hooks/useColorScheme";
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
// 👇
const theme = createTheme({
lightColors: {
...Platform.select({
default: lightColors.platform.android,
ios: lightColors.platform.ios,
}),
},
});
// ☝️
export default function RootLayout() {
const colorScheme = useColorScheme();
const [loaded] = useFonts({
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
});
useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
}
}, [loaded]);
if (!loaded) {
return null;
}
return (
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
<RNEThemeProvider theme={theme}>
<Stack>
<Stack.Screen name="(main)" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" />
</Stack>
</RNEThemeProvider>
</ThemeProvider>
);
}
Fügen Sie einen Stack oberhalb des "(main)" Stack im Root-Layout.
<Stack.Screen name="(login)" options={{ headerShown: false }} />
Erstellen Sie ein Layout für die Authentifizierungsbildschirme unter app/(login)/_layout.tsx.
import { Stack } from "expo-router";
import "react-native-reanimated";
export default function LoginLayout() {
return (
<Stack>
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
<Stack.Screen name="forgot-password" options={{ headerShown: false }} />
</Stack>
);
}
Und neben dieser Datei einen weiteren Bildschirm für den app/(login)/forgot-password.tsx Ablauf.
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
import { StyleSheet } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { Link } from "expo-router";
export default function ForgotPasswordView() {
return (
<SafeAreaProvider>
<ThemedView style={styles.container}>
<SafeAreaView style={styles.innerContainer}>
<ThemedText type="title">Forgot password view</ThemedText>
<Link style={styles.link} href="/(login)/(auth)">
Back to Login
</Link>
</SafeAreaView>
</ThemedView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
innerContainer: {
flex: 1,
justifyContent: "space-around",
alignItems: "center",
},
link: {
lineHeight: 30,
fontSize: 16,
},
});
Als Nächstes erstellen Sie das Layout für die Authentifizierungs-Tabs in app/(login)/(auth)/_layout.tsx.
import { Tabs } from "expo-router";
import React from "react";
import { TabBarIcon } from "@/components/navigation/TabBarIcon";
import { Colors } from "@/constants/Colors";
import { useColorScheme } from "@/hooks/useColorScheme";
export default function AuthLayout() {
const colorScheme = useColorScheme();
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? "light"].tint,
headerShown: false,
}}
>
<Tabs.Screen
name="index"
options={{
title: "Login",
tabBarIcon: ({ color, focused }) => (
<TabBarIcon
name={focused ? "log-in" : "log-in-outline"}
color={color}
/>
),
}}
/>
<Tabs.Screen
name="register"
options={{
title: "Register",
tabBarIcon: ({ color, focused }) => (
<TabBarIcon
name={focused ? "person-add" : "person-add-outline"}
color={color}
/>
),
}}
/>
</Tabs>
);
}
Erstellen Sie den Anmeldebildschirm in app/(login)/(auth)/index.tsx.
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
import { StyleSheet } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { Button } from "@rneui/themed";
import { Link } from "expo-router";
import { router } from "expo-router";
export default function LoginView() {
return (
<SafeAreaProvider>
<ThemedView style={styles.container}>
<SafeAreaView style={styles.innerContainer}>
<ThemedText type="title">Hello from the Login view</ThemedText>
<Link style={styles.link} href="/(login)/forgot-password">
Forgot password
</Link>
<Button
onPress={() => {
router.replace("/(main)");
}}
>
Login
</Button>
</SafeAreaView>
</ThemedView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
innerContainer: {
flex: 1,
justifyContent: "space-around",
alignItems: "center",
},
link: {
lineHeight: 30,
fontSize: 16,
},
});
Erstellen Sie den Registrierungsbildschirm in app/(login)/(auth)/register.tsx:
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
import { StyleSheet } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
export default function RegisterView() {
return (
<SafeAreaProvider>
<ThemedView style={styles.container}>
<SafeAreaView style={styles.innerContainer}>
<ThemedText type="title">Register view</ThemedText>
</SafeAreaView>
</ThemedView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
innerContainer: {
flex: 1,
justifyContent: "space-around",
alignItems: "center",
},
});
Jetzt erstellen Sie die Hauptbildschirme.
Löschen Sie alle Dateien in Ihrem app/(main)/ Ordner, da Sie von Grund auf neu beginnen werden.
Das Hauptlayout übernimmt das Umschalten zwischen verschiedenen Navigatoren basierend auf der Plattform. Sie können React Native's Platform Modul verwenden, um zu erkennen, wo Ihre App ausgeführt wird.
Erstellen Sie das Hauptlayout in app/(main)/_layout.tsx.
import { router, Tabs, usePathname } from "expo-router";
import { Drawer } from "expo-router/drawer";
import React from "react";
import { Pressable, Platform, StyleSheet } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { TabBarIcon } from "@/components/navigation/TabBarIcon";
import { Colors } from "@/constants/Colors";
import { useColorScheme } from "@/hooks/useColorScheme";
export default function MainLayout() {
const colorScheme = useColorScheme();
const pathname = usePathname();
const isHome = pathname === "/";
if (Platform.OS === "android") {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<Drawer>
<Drawer.Screen
name="(home)"
options={{
drawerLabel: "Home",
title: "Home",
headerShown: isHome,
}}
/>
<Drawer.Screen
name="settings"
options={{
drawerLabel: "Settings",
title: "Settings",
headerRight: () => (
<Pressable
style={styles.headerButton}
onPress={() => {
// In the real world, you should use a logout function here
// and then auto redirect using the root layout ❗️
router.replace("(login)");
}}
>
<TabBarIcon name="log-out-outline" />
</Pressable>
),
}}
/>
</Drawer>
</GestureHandlerRootView>
);
}
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? "light"].tint,
}}
>
<Tabs.Screen
name="(home)"
options={{
title: "Home",
headerShown: false,
tabBarIcon: ({ color, focused }) => (
<TabBarIcon
name={focused ? "home" : "home-outline"}
color={color}
/>
),
}}
/>
<Tabs.Screen
name="settings"
options={{
title: "Settings",
tabBarIcon: ({ color, focused }) => (
<TabBarIcon name={focused ? "cog" : "cog-outline"} color={color} />
),
headerLeft: () => (
<Pressable
style={styles.headerButton}
onPress={() => {
// In the real world, you should use a logout function here
// and then auto redirect using the root layout ❗️
router.replace("(login)");
}}
>
<TabBarIcon name="log-out-outline" />
</Pressable>
),
}}
/>
</Tabs>
);
}
const styles = StyleSheet.create({
headerButton: {
paddingHorizontal: 16,
},
});
Beachten Sie, wie Sie den Header des Drawers basierend auf dem aktuellen Pfad des Benutzers ausblenden. Wenn sie sich auf der Home-Route befinden, blenden Sie den Header aus. Andernfalls würden sowohl der Header als auch der Stack-Navigator des Home-Bereichs einen Header rendern.
Erstellen Sie nun den Einstellungsbildschirm unter app/(main)/settings.tsx.
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
import { StyleSheet } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
export default function SettingsView() {
return (
<SafeAreaProvider>
<ThemedView style={styles.container}>
<SafeAreaView style={styles.innerContainer}>
<ThemedText type="title">Settings view</ThemedText>
</SafeAreaView>
</ThemedView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
innerContainer: {
flex: 1,
justifyContent: "space-around",
alignItems: "center",
},
});
Erstellen Sie als Nächstes das Layout für den Home-Stack unter app/(main)/(home)/_layout.tsx.
import { Stack } from "expo-router";
import "react-native-reanimated";
export default function HomeLayout() {
return (
<Stack>
<Stack.Screen
name="index"
options={{ headerTitle: "Home", headerShown: false }}
/>
<Stack.Screen
name="options"
options={{ headerTitle: "Options", presentation: "modal" }}
/>
<Stack.Screen name="details" options={{ headerTitle: "Details" }} />
</Stack>
);
}
Sie konfigurieren den Optionsbildschirm so, dass er als Modal in der options Prop dieses Bildschirms angezeigt wird.
Erstellen Sie den Startbildschirm unter app/(main)/(home)/index.tsx.
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
import { Link } from "expo-router";
import { StyleSheet } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
export default function HomeView() {
return (
<SafeAreaProvider>
<ThemedView style={styles.container}>
<SafeAreaView style={styles.innerContainer}>
<ThemedText type="title">Home view</ThemedText>
<Link style={styles.link} href="/(main)/(home)/options">
Options
</Link>
<Link style={styles.link} href="/(main)/(home)/details">
Details
</Link>
</SafeAreaView>
</ThemedView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
innerContainer: {
flex: 1,
justifyContent: "space-around",
alignItems: "center",
},
link: {
lineHeight: 30,
fontSize: 16,
},
});
Erstellen Sie den Detailbildschirm unter app/(main)/(home)/details.tsx.
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
import { StyleSheet } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
export default function DetailsView() {
return (
<SafeAreaProvider>
<ThemedView style={styles.container}>
<SafeAreaView style={styles.innerContainer}>
<ThemedText type="title">Details view</ThemedText>
</SafeAreaView>
</ThemedView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
innerContainer: {
flex: 1,
justifyContent: "space-around",
alignItems: "center",
},
});
Zuletzt erstellen Sie den Optionsbildschirm unter app/(main)/(home)/options.tsx.
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
import { StyleSheet } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
export default function OptionsView() {
return (
<SafeAreaProvider>
<ThemedView style={styles.container}>
<SafeAreaView style={styles.innerContainer}>
<ThemedText type="title">Options view</ThemedText>
</SafeAreaView>
</ThemedView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
innerContainer: {
flex: 1,
justifyContent: "space-around",
alignItems: "center",
},
});
Das war's. So sollte Ihre App aussehen.
Es gibt noch etwas. Dieses Tutorial hat eine Abkürzung verwendet, indem es Ihren Benutzer beim Abmelden manuell weitergeleitet hat. Normalerweise sollten diese Schaltflächen die Benutzersitzung ungültig machen (z. B. die Anmeldeinformationen aus Ihrer Speicherlösung entfernen).
Wenn Sie Benutzer authentifizieren und sie basierend auf ihrem Authentifizierungsstatus weiterleiten möchten, sollten Sie Expos Redirect Komponente in Ihrem Root-Layout verwenden.
import { Redirect } from 'expo-router';
import { useAuth } from '~/features/authentication/hooks';
// ...
const { user } = useAuth();
if (!user) {
return <Redirect href="(login)" />;
}
Auf dem Mac könnte ein Fehler auftreten, wenn Sie versuchen, den iOS-Simulator zu starten.
Error: xcrun simctl boot FE32B4BF-3BE2-44AD-A839-5E8602C4853E exited with non-zero code: 2
An error was encountered processing the command (domain=NSPOSIXErrorDomain, code=2):
Unable to boot device because we cannot determine the runtime bundle.
No such file or directory
So beheben Sie das Problem. Öffnen Sie Xcode, gehen Sie zu Einstellungen > Plattformen und installieren Sie dann die iOS-Plattform.
Öffnen Sie den Simulator und starten Sie ihn.
open -a Simulator && expo start
Nützliche Expo-Befehle:
› Press **?** │ show commands
› Press **j** │ open debugger
› Press **r** │ reload app
› Press **m** │ toggle menu
› Press **o** │ open project code in your editor
Wenn Ihnen dieser Artikel gefallen hat, sollten Sie meinen YouTube-Kanal abonnieren.