跳到主要内容
版本:7.x

导航器嵌套

导航器嵌套意味着在一个导航器的屏幕中渲染另一个导航器,例如

const HomeTabs = createBottomTabNavigator({
screens: {
Feed: FeedScreen,
Messages: MessagesScreen,
},
});

const RootStack = createNativeStackNavigator({
screens: {
Home: {
screen: HomeTabs,
options: {
headerShown: false,
},
},
Profile: ProfileScreen,
},
});
Snack 上尝试

在上面的例子中,HomeTabs 包含一个标签导航器。它也被用于 RootStack 中的堆栈导航器的 Home 屏幕。所以在这里,一个标签导航器被嵌套在一个堆栈导航器中

  • RootStack (堆栈导航器)
    • HomeTabs (标签导航器)
      • Feed (屏幕)
      • Messages (屏幕)
    • Profile (屏幕)

导航器嵌套的工作方式非常像嵌套常规组件。为了实现你想要的行为,通常需要嵌套多个导航器。

导航器嵌套如何影响行为

当嵌套导航器时,有一些事情需要记住

每个导航器都保留自己的导航历史记录

例如,当你在嵌套堆栈导航器中的屏幕内按下返回按钮时,即使父级是另一个导航器,它也会返回到嵌套堆栈内的上一个屏幕。

每个导航器都有自己的选项

例如,在子导航器中嵌套的屏幕中指定 title 选项不会影响父导航器中显示的标题。

如果你想实现这种行为,请参阅 嵌套导航器的屏幕选项 指南。如果你在堆栈导航器中渲染标签导航器,并希望在堆栈导航器的头部显示标签导航器中活动屏幕的标题,这将非常有用。

导航器中的每个屏幕都有自己的参数

例如,传递给嵌套导航器中屏幕的任何 params 都位于该屏幕的 route 对象中,并且无法从父导航器或子导航器中的屏幕访问。

如果你需要从子屏幕访问父屏幕的参数,你可以使用 React Context 将参数暴露给子组件。

例如,如果你在嵌套屏幕中调用 navigation.goBack(),只有当你已经位于导航器的第一个屏幕时,它才会返回到父导航器。其他操作(如 navigate)的工作方式类似,即导航将在嵌套导航器中发生,如果嵌套导航器无法处理,则父导航器将尝试处理。在上面的例子中,当在 Feed 屏幕内调用 navigate('Messages') 时,嵌套的标签导航器将处理它,但如果你调用 navigate('Settings'),父堆栈导航器将处理它。

例如,如果你的抽屉导航器内部有一个堆栈,则抽屉的 openDrawercloseDrawertoggleDrawer 等方法也将在堆栈导航器内部屏幕的 navigation 对象上可用。但是,假设你有一个堆栈导航器作为抽屉的父级,那么堆栈导航器内部的屏幕将无法访问这些方法,因为它们没有嵌套在抽屉内部。

同样,如果你的堆栈导航器内部有一个标签导航器,则标签导航器中的屏幕将在其 navigation 对象中获得堆栈的 pushreplace 方法。

如果你需要从父级向嵌套的子导航器分发操作,你可以使用 navigation.dispatch

navigation.dispatch(DrawerActions.toggleDrawer());

嵌套导航器不接收父级的事件

例如,如果你的标签导航器内部嵌套了一个堆栈导航器,则堆栈导航器中的屏幕将不会接收父标签导航器发出的事件,例如使用 navigation.addListener 时的 (tabPress)。

要接收来自父导航器的事件,你可以使用 navigation.getParent 显式监听父级的事件

const unsubscribe = navigation
.getParent('MyTabs')
.addListener('tabPress', (e) => {
// Do something
alert('Tab pressed!');
});
Snack 上尝试

这里的 'MyTabs' 指的是你在要监听其事件的父标签导航器的 id 中传递的值。

父导航器的 UI 在子导航器的顶部渲染

例如,当你在抽屉导航器内部嵌套一个堆栈导航器时,你会看到抽屉出现在堆栈导航器的头部上方。但是,如果你将抽屉导航器嵌套在堆栈内部,则抽屉将出现在堆栈的头部下方。这是在决定如何嵌套导航器时要考虑的重要一点。

在你的应用程序中,你可能会根据你想要的行为使用这些模式

  • 标签导航器嵌套在堆栈导航器的初始屏幕内 - 当你推送新屏幕时,新屏幕会覆盖标签栏。
  • 抽屉导航器嵌套在堆栈导航器的初始屏幕内,且初始屏幕的堆栈头部隐藏 - 抽屉只能从堆栈的第一个屏幕打开。
  • 堆栈导航器嵌套在抽屉导航器的每个屏幕内 - 抽屉出现在堆栈的头部上方。
  • 堆栈导航器嵌套在标签导航器的每个屏幕内 - 标签栏始终可见。通常,再次按下标签也会将堆栈弹出到顶部。

考虑以下示例

const MoreTabs = createBottomTabNavigator({
screens: {
Feed: FeedScreen,
Messages: MessagesScreen,
},
});

const RootStack = createNativeStackNavigator({
screens: {
Home: HomeScreen,
More: {
screen: MoreTabs,
options: {
headerShown: false,
},
},
},
});
Snack 上尝试

在这里,你可能想从你的 HomeScreen 组件导航到 More 屏幕(其中包含 MoreTabs

navigation.navigate('More');

它有效,并且显示了 MoreTabs 组件内部的初始屏幕,即 Feed。但有时你可能想控制导航时应显示的屏幕。为了实现这一点,你可以在 params 中传递屏幕名称

navigation.navigate('More', { screen: 'Messages' });

现在,Messages 屏幕将在导航时渲染,而不是 Feed

向嵌套导航器中的屏幕传递参数

你还可以通过指定 params 键来传递参数

navigation.navigate('More', {
screen: 'Messages',
params: { user: 'jane' },
})
Snack 上尝试

如果导航器已经渲染,导航到另一个屏幕将在堆栈导航器的情况下推送一个新屏幕。

你可以对深度嵌套的屏幕采用类似的方法。请注意,此处 navigate 的第二个参数只是 params,因此你可以执行以下操作

navigation.navigate('Home', {
screen: 'Settings',
params: {
screen: 'Sound',
params: {
screen: 'Media',
},
},
});

在上面的例子中,你正在导航到 Media 屏幕,该屏幕在一个嵌套在 Sound 屏幕内的导航器中,而 Sound 屏幕又在一个嵌套在 Settings 屏幕内的导航器中。

警告

screen 和相关参数保留供内部使用,并由 React Navigation 管理。虽然你可以在父屏幕中访问 route.params.screen 等,但依赖它们可能会导致意外行为。

渲染导航器中定义的初始路由

默认情况下,当你在嵌套导航器中导航屏幕时,指定的屏幕将用作初始屏幕,并且导航器上的 initialRouteName prop 将被忽略。

如果你需要渲染导航器中指定的初始路由,你可以通过设置 initial: false 来禁用将指定屏幕用作初始屏幕的行为

navigation.navigate('Root', {
screen: 'Settings',
initial: false,
});

这会影响按下返回按钮时发生的情况。当有初始屏幕时,返回按钮会将用户带到那里。

避免嵌套时出现多个头部

当嵌套多个堆栈、抽屉或底部标签导航器时,将显示来自子导航器和父导航器的头部。但是,通常更希望在子导航器中显示头部,并在父导航器的屏幕中隐藏头部。

为了实现这一点,你可以使用 headerShown: false 选项在包含导航器的屏幕中隐藏头部。

例如

const HomeTabs = createBottomTabNavigator({
screens: {
Feed: FeedScreen,
Messages: MessagesScreen,
},
});

const RootStack = createNativeStackNavigator({
screens: {
Home: {
screen: HomeTabs,
options: {
headerShown: false,
},
},
Profile: ProfileScreen,
},
});
Snack 上尝试

在这些示例中,我们使用了直接嵌套在另一个堆栈导航器中的底部标签导航器,但当中间有其他导航器时,相同的原则也适用,例如:堆栈导航器内部的标签导航器,而标签导航器又在另一个堆栈导航器内部,堆栈导航器内部的抽屉导航器等。

如果你不希望任何导航器中出现头部,你可以在所有导航器中指定 headerShown: false

const HomeTabs = createBottomTabNavigator({
screenOptions: {
headerShown: false,
},
screens: {
Feed: FeedScreen,
Messages: MessagesScreen,
},
});

const RootStack = createStackNavigator({
screenOptions: {
headerShown: false,
},
screens: {
Home: HomeTabs,
Profile: ProfileScreen,
},
});

嵌套时的最佳实践

我们建议将导航器嵌套减少到最少。尽量用尽可能少的嵌套来实现你想要的行为。嵌套有很多缺点

  • 它会导致深度嵌套的视图层次结构,这可能会在低端设备中导致内存和性能问题
  • 嵌套相同类型的导航器(例如,标签页内的标签页,抽屉内的抽屉等)可能会导致令人困惑的 UX
  • 过度嵌套会导致代码在导航到嵌套屏幕、配置深度链接等时变得难以理解。

将导航器嵌套视为实现你想要的 UI 的一种方式,而不是组织代码的方式。如果你想为组织创建单独的屏幕组,而不是使用单独的导航器,你可以使用 Group 组件进行动态配置,或使用 groups 属性 进行静态配置。

const MyStack = createStackNavigator({
screens: {
// Common screens
},
groups: {
// Common modal screens
Modal: {
screenOptions: {
presentation: 'modal',
},
screens: {
Help,
Invite,
},
},
// Screens for logged in users
User: {
if: useIsLoggedIn,
screens: {
Home,
Profile,
},
},
// Auth screens
Guest: {
if: useIsGuest,
screens: {
SignIn,
SignUp,
},
},
},
});