首页指南参考教程

了解如何使用 Expo Router 中的 Stack 导航器。


堆栈导航器是在应用中的路由之间导航的基本方式。在 Android 上,堆叠路由会在当前屏幕顶部动画。在 iOS 上,堆叠路由从右侧动画显示。Expo Router 提供了一个 Stack 导航组件,可创建导航堆栈并允许你在应用中添加新路由。

¥A stack navigator is the foundational way of navigating between routes in an app. On Android, a stacked route animates on top of the current screen. On iOS, a stacked route animates from the right. Expo Router provides a Stack navigation component that creates a navigation stack and allows you to add new routes in your app.

本指南提供有关如何在项目中创建 Stack 导航器以及自定义单个路由的选项和标题的信息。

¥This guide provides information on how you can create a Stack navigator in your project and customize an individual route's options and header.

开始使用

¥Get started

你可以使用基于文件的路由来创建堆栈导航器。这是一个示例文件结构:

¥You can use file-based routing to create a stack navigator. Here's an example file structure:

app
_layout.tsx
index.tsx
details.tsx

此文件结构生成一个布局,其中 index 路由是堆栈中的第一个路由,而 details 路由在导航时被推到 index 路由的顶部。

¥This file structure produces a layout where the index route is the first route in the stack, and the details route is pushed on top of the index route when navigated.

你可以使用 app/_layout.tsx 文件通过以下两个路由定义应用的 Stack 导航器:

¥You can use the app/_layout.tsx file to define your app's Stack navigator with these two routes:

app/_layout.tsx
import { Stack } from 'expo-router/stack';

export default function Layout() {
  return <Stack />;
}

屏幕选项和标题配置

¥Screen options and header configuration

静态配置路由选项

¥Statically configure route options

你可以在布局组件路由中使用 <Stack.Screen name={routeName} /> 组件来静态配置路由的选项。这对 tabsdrawers 也很有用,因为它们需要提前定义图标。

¥You can use the <Stack.Screen name={routeName} /> component in the layout component route to statically configure a route's options. This is also useful for tabs or drawers as they need an icon defined ahead of time.

app/_layout.tsx
import { Stack } from 'expo-router';

export default function Layout() {
  return (
    <Stack
      screenOptions={{
        headerStyle: {
          backgroundColor: '#f4511e',
        },
        headerTintColor: '#fff',
        headerTitleStyle: {
          fontWeight: 'bold',
        },
      }}>
      

{/* Optionally configure static options outside the route.*/}


      <Stack.Screen name="home" options={{}} />
    </Stack>
  );
}

作为 <Stack.Screen> 组件的替代方案,你可以使用 navigation.setOptions() 从路由的组件文件中配置路由的选项。

¥As an alternative to the <Stack.Screen> component, you can use navigation.setOptions() to configure a route's options from within the route's component file.

app/index.tsx
import { Stack, useNavigation } from 'expo-router';
import { Text, View } from 'react-native';
import { useEffect } from 'react';

export default function Home() {
  const navigation = useNavigation();

  useEffect(() => {
    navigation.setOptions({ headerShown: false });
  }, [navigation]);

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
    </View>
  );
}

配置标题栏

¥Configure header bar

你可以使用 screenOptions 属性配置 Stack 导航器中所有路由的标题栏。这对于在所有路由上设置通用标题样式很有用。

¥You can configure the header bar for all routes in a Stack navigator by using the screenOptions prop. This is useful for setting a common header style across all routes.

app/_layout.tsx
import { Stack } from 'expo-router';

export default function Layout() {
  return (
    <Stack
      screenOptions={{
        headerStyle: {
          backgroundColor: '#f4511e',
        },
        headerTintColor: '#fff',
        headerTitleStyle: {
          fontWeight: 'bold',
        },
      }}
    />
  );
}

要为单个路由动态配置标题栏,请在路由文件中使用该导航器的 <Stack.Screen> 组件。这对于更改 UI 的交互很有用。

¥To configure the header bar dynamically for an individual route, use that navigator's <Stack.Screen> component in the routes's file. This is useful for interactions that change the UI.

app/index.tsx
import { Link, Stack } from 'expo-router';
import { Image, Text, View, StyleSheet } from 'react-native';

function LogoTitle() {
  return (
    <Image style={styles.image} source={{ uri: 'https://rn.nodejs.cn/img/tiny_logo.png' }} />
  );
}

export default function Home() {
  return (
    <View style={styles.container}>
      <Stack.Screen
        options={{
          title: 'My home',
          headerStyle: { backgroundColor: '#f4511e' },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
          },

          headerTitle: props => <LogoTitle {...props} />,
        }}
      />
      <Text>Home Screen</Text>
      <Link href={{ pathname: 'details', params: { name: 'Bacon' }}}>Go to Details</Link>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  image: {
    width: 50,
    height: 50,
  },
});

动态设置屏幕选项

¥Set screen options dynamically

要动态配置路由选项,你始终可以使用该路由文件中的 <Stack.Screen> 组件。

¥To configure a route's option dynamically, you can always use the <Stack.Screen> component in that route's file.

作为替代方案,你也可以使用 命令式 API 的 router.setParams() 函数动态配置路由。

¥As an alternative, you can also use the imperative API's router.setParams() function to configure the route dynamically.

app/details.tsx
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
import { View, Text, StyleSheet } from 'react-native';

export default function Details() {
  const router = useRouter();
  const params = useLocalSearchParams();

  return (
    <View style={styles.container}>
      <Stack.Screen
        options={{
          title: params.name,
        }}
      />
      <Text
        onPress={() => {
          router.setParams({ name: 'Updated' });
        }}>
        Update the title
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
});

标题按钮

¥Header buttons

你可以使用 headerLeftheaderRight 选项向标题添加按钮。这些选项接受在标题中渲染的 React 组件。

¥You can add buttons to the header by using the headerLeft and headerRight options. These options accept a React component that renders in the header.

app/index.tsx
import { Stack } from 'expo-router';
import { Button, Text, Image, StyleSheet } from 'react-native';
import { useState } from 'react';

function LogoTitle() {
  return (
    <Image style={styles.image} source={{ uri: 'https://rn.nodejs.cn/img/tiny_logo.png' }} />
  );
}

export default function Home() {
  const [count, setCount] = useState(0);

  return (
    <>
      <Stack.Screen
        options={{
          headerTitle: props => <LogoTitle {...props} />,
          headerRight: () => <Button onPress={() => setCount(c => c + 1)} title="Update count" />,
        }}
      />
      <Text>Count: {count}</Text>
    </>
  );
}

const styles = StyleSheet.create({
  image: {
    width: 50,
    height: 50,
  },
});

自定义推送行为

¥Custom push behavior

默认情况下,Stack 导航器在推送堆栈中已有的路由时会删除重复的屏幕。例如,如果你两次按下同一个屏幕,第二次按下将被忽略。你可以通过向 <Stack.Screen> 提供自定义 getId() 函数来更改此推送行为。

¥By default, the Stack navigator removes duplicate screens when pushing a route that is already in the stack. For example, if you push the same screen twice, the second push will be ignored. You can change this push behavior by providing a custom getId() function to the <Stack.Screen>.

例如,以下布局结构中的 index 路由显示应用中不同用户配置文件的列表。让我们将 [details] 路由设为 动态路由,以便应用用户可以导航查看配置文件的详细信息。

¥For example, the index route in the following layout structure shows a list of different user profiles in the app. Let's make the [details] route a dynamic route so that the app user can navigate to see a profile's details.

app
_layout.tsx
index.tsx
[details].tsxmatches dynamic paths like '/details1'

每次应用用户导航到不同的配置文件时,Stack 导航器都会推送一个新屏幕,但会失败。如果你提供每次都返回新 ID 的 getId() 函数,则每次应用用户导航到个人资料时,Stack 都会推送一个新屏幕。

¥The Stack navigator will push a new screen every time the app user navigates to a different profile but will fail. If you provide a getId() function that returns a new ID every time, the Stack will push a new screen every time the app user navigates to a profile.

你可以在布局组件路由中使用 <Stack.Screen name="[profile]" getId={}> 组件来修改推送行为:

¥You can use the <Stack.Screen name="[profile]" getId={}> component in the layout component route to modify the push behavior:

app/_layout.tsx
import { Stack } from 'expo-router';

export default function Layout() {
  return (
    <Stack>
      <Stack.Screen
        name="[profile]"
        getId={
          ({ params }) => String(Date.now())
        }
      />
    </Stack>
  );
}

删除堆栈屏幕

¥Removing stack screens

你可以使用不同的操作来关闭和删除堆栈中的一个或多个路由。

¥There are different actions you can use to dismiss and remove one or many routes from a stack.

dismiss 行动

¥dismiss action

关闭最近堆栈中的最后一个屏幕。如果当前屏幕是堆栈中的唯一路由,它将关闭整个堆栈。

¥Dismisses the last screen in the closest stack. If the current screen is the only route in the stack, it will dismiss the entire stack.

你可以选择传递一个正数来关闭指定数量的屏幕。

¥You can optionally pass a positive number to dismiss up to that specified number of screens.

Dismiss 与 back 不同,因为它针对最近的堆栈而不是当前导航器。如果你有嵌套导航器,调用 dismiss 将带你返回多个屏幕。

¥Dismiss is different from back as it targets the closest stack and not the current navigator. If you have nested navigators, calling dismiss will take you back multiple screens.

app/settings.tsx
import { Button, View } from 'react-native';
import { useRouter } from 'expo-router';

export default function Settings() {
  const router = useRouter();

  const handleDismiss = (count: number) => {
    router.dismiss(count)
  };

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button title="Go to first screen" onPress={() => handleDismiss(3)} />
    </View>
  );
}

dismissAll 行动

¥dismissAll action

要返回最近堆栈中的第一个屏幕。这与 popToTop 堆栈操作类似。

¥To return to the first screen in the closest stack. This is similar to popToTop stack action.

例如,home 路由是第一个屏幕,settings 是最后一个。要从 settings 转到 home 路由,你必须返回 details。但是,使用 dismissAll 操作,你可以从 settings 转到 home 并关闭中间的任何屏幕。

¥For example, the home route is the first screen, and the settings is the last. To go from settings to home route you'll have to go back to details. However, using the dismissAll action, you can go from settings to home and dismiss any screen in between.

app/settings.tsx
import { Button, View, Text } from 'react-native';
import { useRouter } from 'expo-router';

export default function Settings() {
  const router = useRouter();

  const handleDismissAll = () => {
    router.dismissAll()
  };

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button title="Go to first screen" onPress={handleDismissAll} />
    </View>
  );
}

canDismiss 行动

¥canDismiss action

要检查是否可以关闭当前屏幕。如果路由位于堆栈中,并且堆栈历史记录中有多个屏幕,则返回 true

¥To check if it is possible to dismiss the current screen. Returns true if the router is within a stack with more than one screen in the stack's history.

app/settings.tsx
import { Button, View } from 'react-native';
import { useRouter } from 'expo-router';

export default function Settings() {
  const router = useRouter();

  const handleDismiss = (count: number) => {
    if (router.canDismiss()) {
      router.dismiss(count)
    }
  };

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button title="Maybe dismiss" onPress={() => handleDismiss()} />
    </View>
  );
}

与 Native Stack Navigator 的关系

¥Relation with Native Stack Navigator

Expo Router 中的 Stack 导航器封装了 React Navigation 中的 原生堆栈导航器。Native Stack Navigator 中可用的选项在 Expo Router 中的 Stack 导航器中均可用。

¥The Stack navigator in Expo Router wraps the Native Stack Navigator from React Navigation. Options available in the Native Stack Navigator are all available in the Stack navigator in Expo Router.

使用 @react-navigation/stack 的 JavaScript 堆栈

¥JavaScript stack with @react-navigation/stack

你还可以使用 JavaScript 驱动的 @react-navigation/stack 库通过将此库与 withLayoutContext 封装在一起来创建自定义布局组件。

¥You can also use the JavaScript-powered @react-navigation/stack library to create a custom layout component by wrapping this library with the withLayoutContext.

在以下示例中,JsStack 组件是使用 @react-navigation/stack 库定义的:

¥In the following example, JsStack component is defined using @react-navigation/stack library:

layouts/js-stack.tsx
import { ParamListBase, StackNavigationState } from '@react-navigation/native';
import {
  createStackNavigator,
  StackNavigationEventMap,
  StackNavigationOptions,
} from '@react-navigation/stack';
import { withLayoutContext } from 'expo-router';

const { Navigator } = createStackNavigator();

export const JsStack = withLayoutContext<
  StackNavigationOptions,
  typeof Navigator,
  StackNavigationState<ParamListBase>,
  StackNavigationEventMap
>(Navigator);

定义 JsStack 组件后,你可以在应用中使用它:

¥After defining the JsStack component, you can use it in your app:

app/_layout.tsx
import { JsStack } from '../layouts/js-stack';

export default function Layout() {
  return (
    <JsStack
      screenOptions={
        {
          %%placeholder-start%%... %%placeholder-end%%
        }
      }
    />
  );
}

有关可用选项的更多信息,请参阅 @react-navigation/stack 文档

¥For more information on available options, see @react-navigation/stack documentation.

更多

¥More

Native Stack Navigator 选项

有关堆栈布局中所有可用选项的列表,请参阅 React Navigation 的文档。

Expo 中文网 - 粤ICP备13048890号