堆
了解如何使用 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.tsxindex.tsxdetails.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:
import { Stack } from 'expo-router'; export default function Layout() { return <Stack />; }
屏幕选项和标题配置
🌐 Screen options and header configuration
从 SDK 55 开始,你可以使用基于选项的 API 或新的组合组件 API 配置屏幕选项和标题。这两种 API 在你的项目中都可以互换使用。
🌐 Starting in SDK 55, you can configure screen options and the header using either the options-based API or the new composition components API. Both APIs can be used interchangeably in your project.
静态配置路由选项
🌐 Statically configure route options
你可以在布局组件路由中使用 <Stack.Screen name={routeName} /> 组件来静态配置路由的选项。
🌐 You can use the <Stack.Screen name={routeName} /> component in the layout component route to statically configure a route's options.
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> ); }
配置标题栏
🌐 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.
import { Stack } from 'expo-router'; export default function Layout() { return ( <Stack screenOptions={{ headerStyle: { backgroundColor: '#f4511e', }, headerTintColor: '#fff', headerTitleStyle: { fontWeight: 'bold', }, }} /> ); }
动态设置屏幕选项
🌐 Set screen options dynamically
要动态配置路由的选项,你可以使用组合组件或基于选项的 API。
🌐 To configure a route's options dynamically, you can use either the composition components or the options-based API.
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, headerStyle: { backgroundColor: 'lightblue' }, }} /> <Text onPress={() => { router.setParams({ name: 'Updated' }); }}> Update the title </Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, });
重要 屏幕组成 API 目前处于 alpha 阶段,可在 SDK 55 及更高版本中使用。
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.Title>{params.name}</Stack.Screen.Title> <Stack.Header style={{ backgroundColor: 'lightblue' }} /> <Text onPress={() => { router.setParams({ name: 'Updated' }); }}> Update the title </Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, });
可用的标头选项
🌐 Available header options
Stack 导航器支持全面的标题配置选项。以下是所有可用的标题相关选项:
🌐 The Stack navigator supports comprehensive header configuration options. Below are all the header-related options available:
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. It receives the following properties in the arguments:
|
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 |
unstable_headerLeftItems | iOS | This option is experimental and may change in a minor release. Function which returns an array of items to display as on the left side of the header. This will override
See Header items for more information. |
unstable_headerRightItems | iOS | This option is experimental and may change in a minor release. Function which returns an array of items to display as on the right side of the header. This will override
See Header items for more information. |
有关更多详情和特定导航器示例,请参阅 React Navigation 的原生堆栈导航器文档。
🌐 For additional details and navigator-specific examples, see React Navigation's Native Stack Navigator documentation.
标题按钮
🌐 Header buttons
你可以使用 headerLeft 和 headerRight 选项或 <Stack.Toolbar> 组件向头部添加按钮。这些选项接受在头部渲染的 React 组件。
🌐 You can add buttons to the header by using the headerLeft and headerRight options or <Stack.Toolbar> component. These options accept a React component that renders in the header.
配置 iOS 头部工具栏以支持液态玻璃效果。
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, }, });
重要 屏幕组成 API 目前处于 alpha 阶段,可在 SDK 55 及更高版本中使用。
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.Title asChild> <LogoTitle /> </Stack.Screen.Title> <Stack.Toolbar placement="right" asChild> <Button onPress={() => setCount(c => c + 1)} title="Update count" /> </Stack.Toolbar> <Text>Count: {count}</Text> </> ); } const styles = StyleSheet.create({ image: { width: 50, height: 50, }, });
其他屏幕选项
🌐 Other screen options
有关所有可用其他屏幕选项(包括动画、手势和其他配置)的完整列表:
🌐 For a complete list of all available other screen options including animations, gestures, and other configurations:
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: Defaults to 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 |
tabBarAccessibilityLabel | Android iOS | Accessibility label for the tab button. This is read by the screen reader when the user taps the tab. It's recommended to set this if you don't have a label for the tab. |
tabBarActiveBackgroundColor | Android iOS | Background color for the active tab. |
tabBarActiveTintColor | Android iOS | Color for the icon and label in the active tab. |
tabBarBackground | Android iOS | Function which returns a React Element to use as background for the tab bar. You could render an image, a gradient, blur view etc.:
When using |
tabBarBadge | Android iOS | Text to show in a badge on the tab icon. Accepts a |
tabBarBadgeStyle | Android iOS | Style for the badge on the tab icon. You can specify a background color or text color here. |
tabBarButton | Android iOS | Function which returns a React element to render as the tab bar button. It wraps the icon and label. Renders You can specify a custom implementation here:
|
tabBarButtonTestID | Android iOS | ID to locate this tab button in tests. |
tabBarHideOnKeyboard | Android iOS | Whether the tab bar is hidden when the keyboard opens. Defaults to |
tabBarIcon | Android iOS | Function that given |
tabBarIconStyle | Android iOS | Style object for the tab icon. |
tabBarInactiveBackgroundColor | Android iOS | Background color for the inactive tabs. |
tabBarInactiveTintColor | Android iOS | Color for the icon and label in the inactive tabs. |
tabBarItemStyle | Android iOS | Style object for the tab item container. |
tabBarLabel | Android iOS | Title string of a tab displayed in the tab bar or a function that given |
tabBarLabelPosition | Android iOS | Whether the label is shown below the icon or beside the icon. By default, the position is chosen automatically based on device width.
|
tabBarLabelStyle | Android iOS | Style object for the tab label. |
tabBarPosition | Android iOS | Position of the tab bar. Available values are:
When the tab bar is positioned on the
|
tabBarShowLabel | Android iOS | Whether the tab label should be visible. Defaults to |
tabBarStyle | Android iOS | Style object for the tab bar. You can configure styles such as background color here. To show your screen under the tab bar, you can set the
You also might need to add a bottom margin to your content if you have an absolutely positioned tab bar. React Navigation won't do it automatically. See |
tabBarVariant | Android iOS | Variant of the tab bar. Available values are:
The |
有关更多详情和特定导航器示例,请参阅 React Navigation 的原生堆栈导航器文档。
🌐 For additional details and navigator-specific examples, see React Navigation's Native Stack Navigator documentation.
自定义推送行为
🌐 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.tsxindex.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:
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.
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="返回主屏幕" onPress={() => handleDismiss(3)} /> </View> ); }
dismissTo 操作
🌐 dismissTo action
dismissTo在 Expo Router4.0.8中被添加。它的操作方式类似于 Expo Router v3 中的navigation函数。
在当前的 <Stack /> 中关闭屏幕,直到达到指定的 Href。如果历史记录中没有 Href,则将执行 push 操作。
🌐 Dismisses screens in the current <Stack /> until the specified Href is reached. If the Href is absent in the history, a push action will be performed instead.
例如,考虑 /one、/two、/three 路由的历史,其中 /three 是当前路由。操作 router.dismissTo('/one') 会使历史回退两步,而 router.dismissTo('/four') 会将历史向前推进到 /four 路由。
🌐 For example, consider the history of /one, /two, /three routes, where /three is the current route. The action router.dismissTo('/one') will cause the history to go back twice, while router.dismissTo('/four') will push the history forward to the /four route.
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="返回主屏幕" onPress={handleDismissAll} /> </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.
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="返回主屏幕" 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.
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="也许忽略" onPress={() => handleDismiss()} /> </View> ); }
与 Native Stack Navigator 的关系
🌐 Relation with Native Stack Navigator
Expo Router 中的 Stack 导航器封装了来自 React Navigation 的 Native Stack Navigator。在 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:
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:
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.
iOS 26 液态玻璃标题
🌐 iOS 26 Liquid Glass headers
从 iOS 26 开始,导航栏标题默认采用系统的“液态玻璃”效果。无法针对单个页面禁用,因此需要通过全局配置来选择退出。
🌐 Starting from iOS 26, navigation headers adopt the system's "Liquid Glass" effect by default. It cannot be disabled per screen, so you need to opt out using a global configuration.
方法1:使用 UIDesignRequiresCompatibility
🌐 Method 1: Use UIDesignRequiresCompatibility
注意:在 Expo Go 中不支持。此方法是一个临时解决方案。从 iOS 27 开始,此选项将被苹果移除,你无法选择退出液态玻璃效果。
创建一个开发版本,并在应用配置中将UIDesignRequiresCompatibility属性设置为true:
🌐 Create a development build and set the UIDesignRequiresCompatibility property to true in app config:
{ "ios": { "infoPlist": { "UIDesignRequiresCompatibility": true } } }
方法二:使用基于 JavaScript 的导航栈
🌐 Method 2: Use JavaScript-based navigation stack
从原生导航库(@react-navigation/native)切换到基于 JavaScript 的堆栈导航库,例如 @react-navigation/stack,这样可以让你完全控制头部 UI,但代价是放弃了使用高度优化的 iOS 导航视图/控制器所带来的性能优势。
🌐 Switch from native navigation library (@react-navigation/native) to a JavaScript-based stack navigator library such as @react-navigation/stack, which gives you full control over the header UI but at the cost of performance benefits of using the highly optimized iOS navigation views/controllers.
欲了解更多信息,请参见 带有 @react-navigation/stack 的 JavaScript 堆栈。
🌐 For more information, see JavaScript stack with @react-navigation/stack.
常见问题
🌐 Common problems
大标题在滚动时不会收缩
当在 ScrollView 或 FlatList 中使用 headerLargeTitle: true(或 <Stack.Screen.Title large>)时,大标题可能在滚动时不会收起。这种情况发生在可滚动视图不是屏幕组件的直接第一个子组件时。
🌐 When using headerLargeTitle: true (or <Stack.Screen.Title large>) with a ScrollView or FlatList, the large title may not collapse on scroll. This happens when the scrollable view is not the direct first child of the screen component.
为了解决此问题,请确保 ScrollView 或 FlatList 是你的屏幕组件渲染的第一个子元素。如果需要一个封装器,请在其上设置 collapsable={false}:
🌐 To fix this, ensure ScrollView or FlatList is the first child rendered by your screen component. If you need a wrapper, set collapsable={false} on it:
import { Stack } from 'expo-router'; import { ScrollView, View, Text } from 'react-native'; export default function Home() { return ( <ScrollView> <Stack.Screen.Title large>Home</Stack.Screen.Title> <Text>Content here</Text> </ScrollView> ); }
如果你需要封装 ScrollView,请在封装器上设置 collapsable={false}:
🌐 If you need to wrap the ScrollView, set collapsable={false} on the wrapper:
import { Stack } from 'expo-router'; import { ScrollView, View, Text } from 'react-native'; export default function Home() { return ( <View collapsable={false}> <ScrollView> <Stack.Screen.Title large>Home</Stack.Screen.Title> <Text>Content here</Text> </ScrollView> </View> ); }
在屏幕之间切换时背景会闪白
屏幕切换之间出现的白色闪光通常意味着导航堆栈使用了浅色背景,而你的应用使用的是深色主题。
🌐 A white flash between screen transitions usually means the navigation stack is using a light background while your app uses a dark theme.
要解决此问题,请使用 React Navigation 的 ThemeProvider 封装你的根布局,并传入适当的主题:
🌐 To fix this, wrap your root layout with React Navigation's ThemeProvider and pass the appropriate theme:
import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native'; import { Stack } from 'expo-router'; import { useColorScheme } from 'react-native'; export default function RootLayout() { const colorScheme = useColorScheme(); return ( <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}> <Stack /> </ThemeProvider> ); }
对于始终使用深色主题的应用:
🌐 For apps that are always dark-themed:
import { ThemeProvider, DarkTheme } from '@react-navigation/native'; import { Stack } from 'expo-router'; export default function RootLayout() { return ( <ThemeProvider value={DarkTheme}> <Stack /> </ThemeProvider> ); }