Tutorials
React

Wie man eine React Native App mit komplexer Navigation 2024 erstellt

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.

1. Die App planen

Hier ist ein Diagramm, das zeigt, was Sie erstellen werden.

A diagram displays navigational relationships using solid lines for navigators (tabs, drawer, stack) and dotted lines for screens. It contains eight screens in total. The root stack connects to three elements: a loading screen, auth tabs on iOS, or a main drawer on Android. Within the auth tabs, there are a login stack (which leads to the login screen and password reset screen) and a register screen. The main navigator links to a home stack and a settings screen. The home stack further includes three screens: home, options, and details.

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.

2. Das Basisprojekt erstellen

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-expo

Installieren Sie die Abhängigkeiten für den Drawer.

npx expo install @react-navigation/drawer react-native-gesture-handler react-native-reanimated

Starten 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.

Ladebildschirm

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>
  );
}

React Native Elements

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>
  );
}

3. Erstellen Sie die Authentifizierungsbildschirme

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",
  },
});

4. Erstellen Sie die Hauptbildschirme

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.

Weiterleiten authentifizierter Benutzer

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)" />;
}

Bonus: Fehlerbehebung

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.

Hire reliable React Developers without breaking the bank
Match me with a dev
About the Author
Jan Hesters
CTO, ReactSquad
What's up, this is Jan, CTO of ReactSquad. After studying physics, I ventured into the startup world and became a programmer. As the 7th employee at Hopin, I helped grow the company from a $6 million to a $7.7 billion valuation until it was partly sold in 2023.

Get actionable tips from the ReactSquad team

5-Minute Read. Every Tuesday. For Free