Skip to main content
Version: 6.x

嵌套导航器

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

¥Nesting navigators means rendering a navigator inside a screen of another navigator, for example:

function Home() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={Feed} />
<Tab.Screen name="Messages" component={Messages} />
</Tab.Navigator>
);
}

function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={Home}
options={{ headerShown: false }}
/>
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
</NavigationContainer>
);
}

在上面的示例中,Home 组件包含一个选项卡导航器。Home 组件还用于 App 组件内堆栈导航器中的 Home 屏幕。因此,在这里,选项卡导航器嵌套在堆栈导航器内:

¥In the above example, the Home component contains a tab navigator. The Home component is also used for the Home screen in your stack navigator inside the App component. So here, a tab navigator is nested inside a stack navigator:

  • Stack.Navigator

    • Home(Tab.Navigator)

      • Feed(Screen)

      • Messages(Screen)

    • Profile(Screen)

    • Settings(Screen)

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

¥Nesting navigators work very much like nesting regular components. To achieve the behavior you want, it's often necessary to nest multiple navigators.

嵌套导航器如何影响行为

¥How nesting navigators affects the behaviour

嵌套导航器时,需要记住一些事项:

¥When nesting navigators, there are some things to keep in mind:

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

¥Each navigator keeps its own navigation history

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

¥For example, when you press the back button when inside a screen in a nested stack navigator, it'll go back to the previous screen inside the nested stack even if there's another navigator as the parent.

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

¥Each navigator has its own options

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

¥For example, specifying a title option in a screen nested in a child navigator won't affect the title shown in a parent navigator.

如果你想实现此行为,请参阅 带有嵌套导航器的屏幕选项 的指南。如果你在堆栈导航器内渲染选项卡导航器并希望在堆栈导航器的标题中显示选项卡导航器内活动屏幕的标题,这可能会很有用。

¥If you want to achieve this behavior, see the guide for screen options with nested navigators. this could be useful if you are rendering a tab navigator inside a stack navigator and want to show the title of the active screen inside the tab navigator in the header of the stack navigator.

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

¥Each screen in a navigator has its own params

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

¥For example, any params passed to a screen in a nested navigator are in the route prop of that screen and aren't accessible from a screen in a parent or child navigator.

如果你需要从子屏幕访问父屏幕的参数,你可以使用 React 上下文 将参数公开给子屏幕。

¥If you need to access params of the parent screen from a child screen, you can use React Context to expose params to children.

¥Navigation actions are handled by current navigator and bubble up if couldn't be handled

例如,如果你在嵌套屏幕中调用 navigation.goBack(),则仅当你已位于导航器的第一个屏幕时,它才会返回到父导航器中。其他操作(例如 navigate)的工作原理类似,即导航将在嵌套导航器中进行,如果嵌套导航器无法处理它,则父导航器将尝试处理它。在上面的示例中,当调用 navigate('Messages') 时,在 Feed 屏幕内,嵌套选项卡导航器将处理它,但如果调用 navigate('Settings'),父堆栈导航器将处理它。

¥For example, if you're calling navigation.goBack() in a nested screen, it'll only go back in the parent navigator if you're already on the first screen of the navigator. Other actions such as navigate work similarly, i.e. navigation will happen in the nested navigator and if the nested navigator couldn't handle it, then the parent navigator will try to handle it. In the above example, when calling navigate('Messages'), inside Feed screen, the nested tab navigator will handle it, but if you call navigate('Settings'), the parent stack navigator will handle it.

¥Navigator specific methods are available in the navigators nested inside

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

¥For example, if you have a stack inside a drawer navigator, the drawer's openDrawer, closeDrawer, toggleDrawer methods etc. will also be available on the navigation prop in the screen's inside the stack navigator. But say you have a stack navigator as the parent of the drawer, then the screens inside the stack navigator won't have access to these methods, because they aren't nested inside the drawer.

同样,如果堆栈导航器中有一个选项卡导航器,则选项卡导航器中的屏幕将在其 navigation 属性中获取堆栈的 pushreplace 方法。

¥Similarly, if you have a tab navigator inside stack navigator, the screens in the tab navigator will get the push and replace methods for stack in their navigation prop.

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

¥If you need to dispatch actions to the nested child navigators from a parent, you can use navigation.dispatch:

navigation.dispatch(DrawerActions.toggleDrawer());

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

¥Nested navigators don't receive parent's events

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

¥For example, if you have a stack navigator nested inside a tab navigator, the screens in the stack navigator won't receive the events emitted by the parent tab navigator such as (tabPress) when using navigation.addListener.

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

¥To receive events from parent navigator, you can explicitly listen to parent's events with navigation.getParent:

const unsubscribe = navigation
.getParent('MyTabs')
.addListener('tabPress', (e) => {
// Do something
});

这里 'MyTabs' 指的是你在要监听其事件的父级 Tab.Navigatorid 属性中传递的值。

¥Here 'MyTabs' refers to the value you pass in the id prop of the parent Tab.Navigator whose event you want to listen to.

父导航器的 UI 呈现在子导航器之上

¥Parent navigator's UI is rendered on top of child navigator

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

¥For example, when you nest a stack navigator inside a drawer navigator, you'll see that the drawer appears above the stack navigator's header. However, if you nest the drawer navigator inside a stack, the drawer will appear below the header of the stack. This is an important point to consider when deciding how to nest your navigators.

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

¥In your app, you will probably use these patterns depending on the behavior you want:

  • 选项卡导航器嵌套在堆栈导航器的初始屏幕内 - 当你按下新屏幕时,它们会覆盖选项卡栏。

    ¥Tab navigator nested inside the initial screen of stack navigator - New screens cover the tab bar when you push them.

  • 抽屉导航器嵌套在堆栈导航器的初始屏幕内,隐藏初始屏幕的堆栈标题 - 抽屉只能从堆栈的第一个屏幕打开。

    ¥Drawer navigator nested inside the initial screen of stack navigator with the initial screen's stack header hidden - The drawer can only be opened from the first screen of the stack.

  • 堆栈导航器嵌套在抽屉导航器的每个屏幕内 - 抽屉出现在堆栈的标题上方。

    ¥Stack navigators nested inside each screen of drawer navigator - The drawer appears over the header from the stack.

  • 堆栈导航器嵌套在选项卡导航器的每个屏幕内 - 标签栏始终可见。通常,再次按下选项卡也会将堆栈弹出到顶部。

    ¥Stack navigators nested inside each screen of tab navigator - The tab bar is always visible. Usually pressing the tab again also pops the stack to top.

¥Navigating to a screen in a nested navigator

考虑以下示例:

¥Consider the following example:

function Root() {
return (
<Drawer.Navigator>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Drawer.Navigator>
);
}

function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Root"
component={Root}
options={{ headerShown: false }}
/>
<Stack.Screen name="Feed" component={Feed} />
</Stack.Navigator>
</NavigationContainer>
);
}

在这里,你可能想要从 Feed 组件导航到 Root 屏幕:

¥Here, you might want to navigate to the Root screen from your Feed component:

navigation.navigate('Root');

它可以工作,并显示 Root 组件内的初始屏幕,即 Home。但有时你可能想要控制导航时应显示的屏幕。要实现它,你可以在参数中传递屏幕的名称:

¥It works, and the initial screen inside the Root component is shown, which is Home. But sometimes you may want to control the screen that should be shown upon navigation. To achieve it, you can pass the name of the screen in params:

navigation.navigate('Root', { screen: 'Profile' });

现在,导航时将渲染 Profile 屏幕而不是 Home

¥Now, the Profile screen will be rendered instead of Home upon navigation.

This may look very different from the way navigation used to work with nested screens previously. The difference is that in the previous versions, all configuration was static, so React Navigation could statically find the list of all the navigators and their screens by recursing into nested configurations. But with dynamic configuration, React Navigation doesn't know which screens are available and where until the navigator containing the screen renders. Normally, a screen doesn't render its contents until you navigate to it, so the configuration of navigators which haven't rendered is not yet available. This makes it necessary to specify the hierarchy you're navigating to. This is also why you should have as little nesting of navigators as possible to keep your code simpler.

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

¥Passing params to a screen in a nested navigator

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

¥You can also pass params by specifying a params key:

navigation.navigate('Root', {
screen: 'Profile',
params: { user: 'jane' },
});

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

¥If the navigator was already rendered, navigating to another screen will push a new screen in case of stack navigator.

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

¥You can follow similar approach for deeply nested screens. Note that the second argument to navigate here is just params, so you can do something like:

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

在上述情况下,你将导航到 Media 屏幕,该屏幕位于嵌套在 Sound 屏幕内的导航器中,而 Sound 屏幕位于嵌套在 Settings 屏幕内的导航器中。

¥In the above case, you're navigating to the Media screen, which is in a navigator nested inside the Sound screen, which is in a navigator nested inside the Settings screen.

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

¥Rendering initial route defined in the navigator

默认情况下,当你在嵌套导航器中导航屏幕时,指定的屏幕将用作初始屏幕,并且导航器上的初始路由属性将被忽略。此行为与 React Navigation 4 不同。

¥By default, when you navigate a screen in the nested navigator, the specified screen is used as the initial screen and the initial route prop on the navigator is ignored. This behaviour is different from the React Navigation 4.

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

¥If you need to render the initial route specified in the navigator, you can disable the behaviour of using the specified screen as the initial screen by setting initial: false:

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

这会影响按后退按钮时发生的情况。当出现初始屏幕时,后退按钮会将用户带到那里。

¥This affects what happens when pressing the back button. When there's an initial screen, the back button will take the user there.

嵌套多个导航器

¥Nesting multiple navigators

有时嵌套多个导航器(例如堆栈、抽屉或选项卡)很有用。

¥It's sometimes useful to nest multiple navigators such as stack, drawer or tabs.

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

¥When nesting multiple stack, drawer or bottom tab navigator, headers from both child and parent navigators would be shown. However, usually it's more desirable to show the header in the child navigator and hide the header in the screen of the parent navigator.

为此,你可以使用 headerShown: false 选项隐藏包含导航器的屏幕中的标题。

¥To achieve this, you can hide the header in the screen containing the navigator using the headerShown: false option.

例如:

¥For example:

function Home() {
return (
<Tab.Navigator>
<Tab.Screen name="Profile" component={Profile} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
);
}

function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={Home}
options={{ headerShown: false }}
/>
<Stack.Screen name="EditPost" component={EditPost} />
</Stack.Navigator>
</NavigationContainer>
);
}

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

¥In these examples, we have used a bottom tab navigator directly nested inside another stack navigator, but the same principle applies when there are other navigators in the middle, for example: stack navigator inside a tab navigator which is inside another stack navigator, stack navigator inside drawer navigator etc.

如果你不想在任何导航器中出现标题,则可以在所有导航器中指定 headerShown: false

¥If you don't want headers in any of the navigators, you can specify headerShown: false in all of the navigators:

function Home() {
return (
<Tab.Navigator screenOptions={{ headerShown: false }}>
<Tab.Screen name="Profile" component={Profile} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
);
}

function App() {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="EditPost" component={EditPost} />
</Stack.Navigator>
</NavigationContainer>
);
}

嵌套时的最佳实践

¥Best practices when nesting

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

¥We recommend to reduce nesting navigators to minimal. Try to achieve the behavior you want with as little nesting as possible. Nesting has many downsides:

  • 它会导致深度嵌套的视图层次结构,这可能会导致低端设备中的内存和性能问题

    ¥It results in deeply nested view hierarchy which can cause memory and performance issues in lower end devices

  • 嵌套相同类型的导航器(例如选项卡内的选项卡、抽屉内的抽屉等)可能会导致用户体验混乱

    ¥Nesting same type of navigators (e.g. tabs inside tabs, drawer inside drawer etc.) might lead to a confusing UX

  • 如果嵌套过多,当导航到嵌套屏幕、配置深层链接等时,代码将变得难以理解。

    ¥With excessive nesting, code becomes difficult to follow when navigating to nested screens, configuring deep link etc.

将嵌套导航器视为实现所需 UI 的一种方式,而不是组织代码的一种方式。如果你想创建单独的屏幕组进行组织,而不是使用单独的导航器,你可以使用 Group 组件。

¥Think of nesting navigators as a way to achieve the UI you want rather than a way to organize your code. If you want to create separate group of screens for organization, instead of using separate navigators, you can use the Group component.

<Stack.Navigator>
{isLoggedIn ? (
// Screens for logged in users
<Stack.Group>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Profile" component={Profile} />
</Stack.Group>
) : (
// Auth screens
<Stack.Group screenOptions={{ headerShown: false }}>
<Stack.Screen name="SignIn" component={SignIn} />
<Stack.Screen name="SignUp" component={SignUp} />
</Stack.Group>
)}
{/* Common modal screens */}
<Stack.Group screenOptions={{ presentation: 'modal' }}>
<Stack.Screen name="Help" component={Help} />
<Stack.Screen name="Invite" component={Invite} />
</Stack.Group>
</Stack.Navigator>