堆栈
编辑
学习如何在 Expo Router 中使用堆栈导航器。

在屏幕之间导航、在屏幕之间传递参数、创建动态路由,并配置屏幕标题和动画。
堆栈导航器是应用程序中在路由之间导航的基础方式。在 Android 上,堆叠的路由在当前屏幕上方动画显示。在 iOS 上,堆叠的路由从右侧动画显示。Expo Router 提供了一个 Stack 导航组件,创建一个导航堆栈并允许您在应用中添加新路由。
本指南提供有关如何在项目中创建 Stack 导航器并自定义单个路由的选项和标题的信息。
开始使用
您可以使用基于文件的路由来创建堆栈导航器。下面是一个示例文件结构:
app_layout.tsxindex.tsxdetails.tsx此文件结构生成一个布局,其中 index 路由是堆栈中的第一个路由,而 details 路由在导航时会堆叠在 index 路由之上。
您可以使用 app/_layout.tsx 文件来定义您的应用的 Stack 导航器,并使用这两个路由:
import { Stack } from 'expo-router'; export default function Layout() { return <Stack />; }
屏幕选项和标题配置
静态配置路由选项
您可以在布局组件路由中使用 <Stack.Screen name={routeName} /> 组件来静态配置路由的选项。这对于 tabs 或 drawers 也很有用,因为它们需要提前定义一个图标。
import { Stack } from 'expo-router'; export default function Layout() { return ( <Stack screenOptions={{ headerStyle: { backgroundColor: '#f4511e', }, headerTintColor: '#fff', headerTitleStyle: { fontWeight: 'bold', }, }}> {/* 可选地在路由外部配置静态选项。*/} <Stack.Screen name="home" options={{}} /> </Stack> ); }
作为 <Stack.Screen> 组件的替代,您可以使用 navigation.setOptions() 从路由组件文件内部配置路由的选项。
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> ); }
配置标题栏
您可以使用 screenOptions 属性为 Stack 导航器中的所有路由配置标题栏。这对于在所有路由之间设置共同的标题样式很有用。
import { Stack } from 'expo-router'; export default function Layout() { return ( <Stack screenOptions={{ headerStyle: { backgroundColor: '#f4511e', }, headerTintColor: '#fff', headerTitleStyle: { fontWeight: 'bold', }, }} /> ); }
要为单个路由动态配置标题栏,请在路由的文件中使用该导航器的 <Stack.Screen> 组件。这对于改变 UI 的交互非常有用。
import { Link, Stack } from 'expo-router'; import { Image, Text, View, StyleSheet } from 'react-native'; function LogoTitle() { return ( <Image style={styles.image} source={{ uri: 'https://reactnative.dev/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, }, });
可用的标题选项
Stack 导航器支持全面的标题配置选项。以下是所有可用的与标题相关的选项:
Header options
| Option | Platform | Description |
|---|---|---|
header | Android iOS | Custom header to use instead of the default header. This accepts a function that returns a React Element to display as a header. The function receives an object containing the following properties as the argument:
To set a custom header for all the screens in the navigator, you can specify this option in the Note that if you specify a custom header, the native functionality such as large title, search bar etc. won't work. |
headerBackButtonDisplayMode | iOS | How the back button displays icon and title. Supported values:
The space-aware behavior is disabled when:
In such cases, a static title and icon are always displayed. |
headerBackButtonMenuEnabled | iOS | Boolean indicating whether to show the menu on longPress of iOS >= 14 back button. Defaults to |
headerBackground | Android iOS | Function which returns a React Element to render as the background of the header. This is useful for using backgrounds such as an image or a gradient. |
headerBackImageSource | Android iOS | Image to display in the header as the icon in the back button. Defaults to back icon image for the platform
|
headerBackTitle | iOS | Title string used by the back button on iOS. Defaults to the previous scene's title, "Back" or arrow icon depending on the available space. See Use |
headerBackTitleStyle | iOS | Style object for header back title. Supported properties:
|
headerBackVisible | Android iOS | Whether the back button is visible in the header. You can use it to show a back button alongside This will have no effect on the first screen in the stack. |
headerBlurEffect | iOS | Blur effect for the translucent header. The Supported values: |
headerLargeStyle | iOS | Style of the header when a large title is shown. The large title is shown if Supported properties:
|
headerLargeTitle | iOS | Whether to enable header with large title which collapses to regular header on scroll.
Defaults to For large title to collapse on scroll, the content of the screen should be wrapped in a scrollable view such as |
headerLargeTitleShadowVisible | Android iOS | Whether drop shadow of header is visible when a large title is shown. |
headerLargeTitleStyle | iOS | Style object for large title in header. Supported properties:
|
headerLeft | Android iOS | Function which returns a React Element to display on the left side of the header. This replaces the back button. See |
headerRight | Android iOS | Function which returns a React Element to display on the right side of the header. |
headerSearchBarOptions | iOS | Options to render a native search bar on iOS. Search bars are rarely static so normally it is controlled by passing an object to You also need to specify Supported properties are: ref Ref to manipulate the search input imperatively. It contains the following methods:
autoCapitalize Controls whether the text is automatically auto-capitalized as it is entered by the user. Possible values:
Defaults to autoFocus Whether to automatically focus search bar when it's shown. Defaults to barTintColor The search field background color. By default bar tint color is translucent. tintColor The color for the cursor caret and cancel button text. cancelButtonText The text to be used instead of default disableBackButtonOverride Whether the back button should close search bar's text input or not. Defaults to hideNavigationBar Boolean indicating whether to hide the navigation bar during searching. Defaults to hideWhenScrolling Boolean indicating whether to hide the search bar when scrolling. Defaults to inputType The type of the input. Defaults to Supported values: obscureBackground Boolean indicating whether to obscure the underlying content with semi-transparent overlay. Defaults to placeholder Text displayed when search field is empty. textColor The color of the text in the search field. hintTextColor The color of the hint text in the search field. headerIconColor The color of the search and close icons shown in the header shouldShowHintSearchIcon Whether to show the search hint icon when search bar is focused. Defaults to onBlur A callback that gets called when search bar has lost focus. onCancelButtonPress A callback that gets called when the cancel button is pressed. onChangeText A callback that gets called when the text changes. It receives the current text value of the search bar. |
headerShadowVisible | Android iOS | Whether to hide the elevation shadow (Android) or the bottom border (iOS) on the header. |
headerShown | Android iOS | Whether to show the header. The header is shown by default. Setting this to |
headerStyle | Android iOS | Style object for header. Supported properties:
|
headerTintColor | Android iOS | Tint color for the header. Changes the color of back button and title. |
headerTitle | Android iOS | String or a function that returns a React Element to be used by the header. Defaults to When a function is passed, it receives Note that if you render a custom element by passing a function, animations for the title won't work. |
headerTitleAlign | Android iOS | How to align the header title. Possible values:
Defaults to Not supported on iOS. It's always |
headerTitleStyle | Android iOS | Style object for header title. Supported properties:
|
headerTransparent | Android iOS | Boolean indicating whether the navigation bar is translucent. Defaults to This is useful if you want to render a semi-transparent header or a blurred background. Note that if you don't want your content to appear under the header, you need to manually add a top margin to your content. React Navigation won't do it automatically. To get the height of the header, you can use |
title | Android iOS | String that can be used as a fallback for |
动态设置屏幕选项
要动态配置路由的选项,您可以在该路由的文件中始终使用 <Stack.Screen> 组件。
另外,您还可以使用 命令式 API 的 router.setParams() 函数来动态配置路由。
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', }, });
标题按钮
您可以通过使用 headerLeft 和 headerRight 选项向标题添加按钮。这些选项接受一个 React 组件,该组件在标题中渲染。
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://reactnative.dev/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, }, });
其他屏幕选项
有关包括动画、手势和其他配置的所有可用其他屏幕选项的完整列表:
Screen options
| Option | Platform | Description |
|---|---|---|
animation | Android | How the screen should animate when pushed or popped. Supported values: |
animationDuration | iOS | Changes the duration (in milliseconds) of The duration of |
animationMatchesGesture | iOS | Whether the gesture to dismiss should use animation provided to Doesn't affect the behavior of screens presented modally. |
animationTypeForReplace | Android iOS | The type of animation to use when this screen replaces another screen. Defaults to Supported values: |
autoHideHomeIndicator | iOS | Boolean indicating whether the home indicator should prefer to stay hidden. Defaults to |
contentStyle | Android iOS | Style object for the scene content. |
freezeOnBlur | iOS | Boolean indicating whether to prevent inactive screens from re-rendering. Defaults to Only supported on iOS and Android. |
fullScreenGestureEnabled | iOS | Whether the gesture to dismiss should work on the whole screen. Using gesture to dismiss with this option results in the same transition animation as Doesn't affect the behavior of screens presented modally. |
fullScreenGestureShadowEnabled | Android iOS | Whether the full screen dismiss gesture has shadow under view during transition. Defaults to This does not affect the behavior of transitions that don't use gestures enabled by |
gestureDirection | iOS | Sets the direction in which you should swipe to dismiss the screen. Supported values: When using |
gestureEnabled | iOS | Whether you can use gestures to dismiss this screen. Defaults to |
navigationBarColor | Android | This option is deprecated and will be removed in a future release (for apps targeting Android SDK 35 or above edge-to-edge mode is enabled by default and it is expected that the edge-to-edge will be enforced in future SDKs, see here for more information). Sets the navigation bar color. Defaults to initial status bar color. |
navigationBarHidden | Android | Boolean indicating whether the navigation bar should be hidden. Defaults to |
orientation | Android | The display orientation to use for the screen. Supported values: |
presentation | Android | How should the screen be presented. Supported values: |
sheetAllowedDetents | Android | Works only when Describes heights where a sheet can rest. Supported values: Defaults to |
sheetCornerRadius | Android | Works only when The corner radius that the sheet will try to render with. If set to non-negative value it will try to render sheet with provided radius, else it will apply system default. If left unset, system default is used. |
sheetElevation | Android | Works only when Integer value describing elevation of the sheet, impacting shadow on the top edge of the sheet. Not dynamic - changing it after the component is rendered won't have an effect. Defaults to |
sheetExpandsWhenScrolledToEdge | iOS | Works only when Whether the sheet should expand to larger detent when scrolling. Defaults to Please note that for this interaction to work, the ScrollView must be "first-subview-chain" descendant of the Screen component. This restriction is due to platform requirements. |
sheetGrabberVisible | iOS | Works only when Boolean indicating whether the sheet shows a grabber at the top. Defaults to |
sheetInitialDetentIndex | Android | Works only when Index of the detent the sheet should expand to after being opened. If the specified index is out of bounds of Additionaly there is Defaults to |
sheetLargestUndimmedDetentIndex | Android | Works only when The largest sheet detent for which a view underneath won't be dimmed. This prop can be set to an number, which indicates index of detent in Additionaly there are following options available:
Defaults to |
statusBarAnimation | Android | Sets the status bar animation (similar to the Supported values: On Android, setting either Requires setting |
statusBarBackgroundColor | Android | This option is deprecated and will be removed in a future release (for apps targeting Android SDK 35 or above edge-to-edge mode is enabled by default and it is expected that the edge-to-edge will be enforced in future SDKs, see here for more information). Sets the background color of the status bar (similar to the |
statusBarHidden | Android | Whether the status bar should be hidden on this screen. Requires setting |
statusBarStyle | Android | Sets the status bar color (similar to the Supported values: Requires setting |
statusBarTranslucent | Android | This option is deprecated and will be removed in a future release (for apps targeting Android SDK 35 or above edge-to-edge mode is enabled by default and it is expected that the edge-to-edge will be enforced in future SDKs, see here for more information). Sets the translucency of the status bar (similar to the |
自定义推送行为
默认情况下,Stack 导航器在推送已经在堆栈中的路由时会删除重复的屏幕。例如,如果您推送相同的屏幕两次,则第二个推送将被忽略。您可以通过为 <Stack.Screen> 提供自定义 getId() 函数来更改此推送行为。
例如,以下布局结构中的 index 路由显示了应用中不同用户资料的列表。让我们将 [details] 路由设置为 动态路由,以便应用用户可以浏览查看某个资料的详细信息。
app_layout.tsxindex.tsx[details].tsx匹配动态路径,如 '/details1'每次应用用户导航到不同的资料时,Stack 导航器将推送一个新屏幕,但会失败。如果您提供一个每次返回新 ID 的 getId() 函数,Stack 每次用户导航到一个资料时都会推送一个新屏幕。
您可以在布局组件路由中使用 <Stack.Screen name="[profile]" getId={}> 组件来修改推送行为:
import { Stack } from 'expo-router'; export default function Layout() { return ( <Stack> <Stack.Screen name="[profile]" getId={ ({ params }) => String(Date.now()) } /> </Stack> ); }
删除堆栈屏幕
您可以使用不同的操作来解除和删除堆栈中的一个或多个路由。
dismiss 操作
解除最近堆栈中的最后一个屏幕。如果当前屏幕是堆栈中的唯一路由,它将解除整个堆栈。
您可以可选地传递一个正数,以解除到该指定数量的屏幕。
解除与 back 不同,因为它针对的是最近的堆栈,而不是当前导航器。如果您有嵌套导航器,调用 dismiss 将带您回到多个屏幕。
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> ); }
dismissTo 操作
dismissTo在 Expo Router4.0.8中添加。它的操作类似于 Expo Router v3 中的navigation函数。
解除当前 <Stack /> 中的屏幕,直到达到指定的 Href。如果历史中不存在 Href,则将执行 push 操作。
例如,考虑 /one、/two、/three 路由的历史,其中 /three 是当前路由。操作 router.dismissTo('/one') 将导致历史回退两次,而 router.dismissTo('/four') 将推送历史前进到 /four 路由。
import { Button, View, Text } from 'react-native'; import { useRouter } from 'expo-router'; export default function Settings() { const router = useRouter(); const handleDismissAll = () => { router.dismissTo('/') }; return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Button title="Go to first screen" onPress={handleDismissAll} /> </View> ); }
dismissAll 操作
返回最近堆栈中的第一屏。这与 popToTop 堆栈操作类似。
例如,home 路由是第一屏,settings 是最后一屏。要从 settings 回到 home 路由,您必须先返回到 details。然而,使用 dismissAll 操作,您可以从 settings 直接跳转到 home,并解除中间的任何屏幕。
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 操作
检查是否可以解除当前屏幕。如果路由处于堆栈中,且堆栈历史中有超过一个屏幕,则返回 true。
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> ); }
与原生堆栈导航器的关系
Expo Router 中的 Stack 导航器封装了来自 React Navigation 的 Native Stack Navigator。Native Stack Navigator 中可用的选项在 Expo Router 的 Stack 导航器中都可用。
使用 @react-navigation/stack 的 JavaScript 堆栈
您还可以使用基于 JavaScript 的 @react-navigation/stack 库通过将此库包装在 withLayoutContext 中来创建自定义布局组件。
在以下示例中,JsStack 组件是使用 @react-navigation/stack 库定义的:
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 组件后,您可以在应用中使用它:
import { JsStack } from '../layouts/js-stack'; export default function Layout() { return ( <JsStack screenOptions={ { %%placeholder-start%%... %%placeholder-end%% } } /> ); }
有关可用选项的更多信息,请参见 @react-navigation/stack 文档。
iOS 26 液态玻璃标题
从 iOS 26 开始,导航标题默认采用系统的“液态玻璃”效果。无法按屏幕禁用,因此您需要使用全局配置选择退出。
方法 1:使用 UIDesignRequiresCompatibility
注意:在 Expo Go 中不支持。此方法是临时解决方案。从 iOS 27 开始,此选项将被苹果公司移除,您无法选择退出液态玻璃效果。
创建一个 开发构建,并在 应用配置 中将 UIDesignRequiresCompatibility 属性设置为 true:
{ "ios": { "infoPlist": { "UIDesignRequiresCompatibility": true } } }
方法 2:使用基于 JavaScript 的导航堆栈
从原生导航库 (@react-navigation/native) 切换到 JavaScript 基于的堆栈导航库,如 @react-navigation/stack,它让您完全控制标题 UI,但以牺牲使用高度优化的 iOS 导航视图/控制器的性能为代价。
有关更多信息,请参见 使用 @react-navigation/stack 的 JavaScript 堆栈。