嵌套导航器
嵌套导航器意味着在一个导航器的屏幕内渲染另一个导航器,例如
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
屏幕。因此,这里标签导航器嵌套在堆栈导航器内
Stack.Navigator
Home
(Tab.Navigator
)Feed
(Screen
)Messages
(Screen
)
Profile
(Screen
)Settings
(Screen
)
嵌套导航器的工作方式与嵌套常规组件非常相似。为了实现您想要的行为,通常需要嵌套多个导航器。
嵌套导航器如何影响行为
嵌套导航器时,有一些事项需要牢记
每个导航器都保留自己的导航历史记录
例如,当您在嵌套堆栈导航器中的屏幕内按下后退按钮时,它将返回到嵌套堆栈内的上一屏幕,即使父级存在另一个导航器。
每个导航器都有自己的选项
例如,在嵌套在子导航器中的屏幕中指定 title
选项不会影响父导航器中显示的标题。
如果您想实现此行为,请参阅有关 嵌套导航器的屏幕选项 的指南。这在您在堆栈导航器中渲染标签导航器并希望在堆栈导航器的标题中显示标签导航器中活动屏幕的标题时很有用。
导航器中的每个屏幕都有自己的参数
例如,传递给嵌套导航器中屏幕的任何 params
都在该屏幕的 route
属性中,并且无法从父导航器或子导航器中的屏幕访问。
如果您需要从子屏幕访问父屏幕的参数,可以使用 React 上下文 将参数公开给子组件。
导航操作由当前导航器处理,如果无法处理,则会冒泡
例如,如果您在嵌套屏幕中调用 navigation.goBack()
,则只有当您已经在导航器的第一个屏幕上时,它才会返回到父导航器。其他操作(如 navigate
)的工作方式类似,即导航将在嵌套导航器中进行,如果嵌套导航器无法处理它,则父导航器将尝试处理它。在上面的示例中,当在 Feed
屏幕中调用 navigate('Messages')
时,嵌套标签导航器将处理它,但如果您调用 navigate('Settings')
,则父堆栈导航器将处理它。
导航器特定的方法在嵌套的导航器中可用
例如,如果您在抽屉导航器中有一个堆栈,则抽屉的openDrawer
、closeDrawer
、toggleDrawer
等方法也将在堆栈导航器内部屏幕的navigation
道具中可用。但是,假设您有一个堆栈导航器作为抽屉的父级,那么堆栈导航器内部的屏幕将无法访问这些方法,因为它们没有嵌套在抽屉内部。
类似地,如果您在堆栈导航器中有一个选项卡导航器,则选项卡导航器中的屏幕将在其navigation
道具中获得堆栈的push
和replace
方法。
如果您需要从父级调度操作到嵌套的子导航器,可以使用navigation.dispatch
navigation.dispatch(DrawerActions.toggleDrawer());
嵌套导航器不会接收父级的事件
例如,如果您在选项卡导航器中嵌套了一个堆栈导航器,则堆栈导航器中的屏幕将不会接收父选项卡导航器发出的事件,例如(tabPress
),当使用navigation.addListener
时。
要接收来自父导航器的事件,您可以使用navigation.getParent
显式监听父级的事件。
const unsubscribe = navigation
.getParent('MyTabs')
.addListener('tabPress', (e) => {
// Do something
});
这里'MyTabs'
指的是您在要监听其事件的父Tab.Navigator
的id
道具中传递的值。
父导航器的 UI 渲染在子导航器之上
例如,当您将堆栈导航器嵌套在抽屉导航器中时,您会看到抽屉出现在堆栈导航器的标题之上。但是,如果您将抽屉导航器嵌套在堆栈中,则抽屉将出现在堆栈标题之下。在决定如何嵌套导航器时,这是一个重要的考虑因素。
在您的应用程序中,您可能会根据所需的行为使用这些模式。
- 嵌套在堆栈导航器初始屏幕中的选项卡导航器 - 当您推送新屏幕时,新屏幕会覆盖选项卡栏。
- 抽屉导航器嵌套在堆栈导航器的初始屏幕内,初始屏幕的堆栈标题隐藏 - 抽屉只能从堆栈的第一个屏幕打开。
- 堆栈导航器嵌套在抽屉导航器的每个屏幕内 - 抽屉出现在堆栈的标题之上。
- 堆栈导航器嵌套在标签导航器的每个屏幕内 - 标签栏始终可见。通常再次按下标签也会将堆栈弹出到顶部。
导航到嵌套导航器中的屏幕
考虑以下示例
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
屏幕
navigation.navigate('Root');
它可以工作,并且 Root
组件内的初始屏幕显示,即 Home
。但有时您可能希望控制导航后应显示的屏幕。为了实现这一点,您可以在参数中传递屏幕的名称
navigation.navigate('Root', { screen: 'Profile' });
现在,导航后将渲染 Profile
屏幕而不是 Home
。
这可能与以前使用嵌套屏幕进行导航的方式大不相同。区别在于,在以前的版本中,所有配置都是静态的,因此 React Navigation 可以通过递归嵌套配置来静态地找到所有导航器及其屏幕的列表。但是,使用动态配置,React Navigation 在包含屏幕的导航器渲染之前不知道哪些屏幕可用以及在哪里。通常,屏幕在您导航到它之前不会渲染其内容,因此尚未渲染的导航器的配置不可用。这使得有必要指定您正在导航到的层次结构。这也是为什么您应该尽可能少地嵌套导航器以使代码更简单的另一个原因。
将参数传递给嵌套导航器中的屏幕
您还可以通过指定 params
键来传递参数
navigation.navigate('Root', {
screen: 'Profile',
params: { user: 'jane' },
});
如果导航器已经渲染,则导航到另一个屏幕将在堆栈导航器的情况下推送一个新屏幕。
您可以对深度嵌套的屏幕遵循类似的方法。请注意,这里 navigate
的第二个参数只是 params
,因此您可以执行以下操作
navigation.navigate('Root', {
screen: 'Settings',
params: {
screen: 'Sound',
params: {
screen: 'Media',
},
},
});
在上述情况下,您正在导航到 Media
屏幕,该屏幕位于嵌套在 Sound
屏幕内的导航器中,而 Sound
屏幕位于嵌套在 Settings
屏幕内的导航器中。
渲染导航器中定义的初始路由
默认情况下,当您在嵌套导航器中导航屏幕时,指定的屏幕将用作初始屏幕,并且导航器上的初始路由道具将被忽略。此行为与 React Navigation 4 不同。
如果您需要渲染导航器中指定的初始路由,可以通过设置initial: false
来禁用将指定屏幕用作初始屏幕的行为。
navigation.navigate('Root', {
screen: 'Settings',
initial: false,
});
这会影响按下后退按钮时的行为。当存在初始屏幕时,后退按钮会将用户带到该屏幕。
嵌套多个导航器
有时将多个导航器(如堆栈、抽屉或选项卡)嵌套在一起很有用。
当嵌套多个堆栈、抽屉或底部选项卡导航器时,子导航器和父导航器的标题都会显示。但是,通常更希望在子导航器中显示标题,而在父导航器的屏幕中隐藏标题。
要实现此目的,可以使用headerShown: false
选项在包含导航器的屏幕中隐藏标题。
例如
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>
);
}
在这些示例中,我们直接将底部选项卡导航器嵌套在另一个堆栈导航器中,但相同的原理适用于中间存在其他导航器的情况,例如:堆栈导航器在选项卡导航器中,选项卡导航器在另一个堆栈导航器中,堆栈导航器在抽屉导航器中等等。
如果您不想在任何导航器中显示标题,可以在所有导航器中指定headerShown: false
。
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>
);
}
嵌套时的最佳实践
我们建议将嵌套导航器减少到最少。尝试使用尽可能少的嵌套来实现您想要的行为。嵌套有很多缺点。
- 它会导致深度嵌套的视图层次结构,这可能会在低端设备上导致内存和性能问题。
- 嵌套相同类型的导航器(例如,选项卡在选项卡中,抽屉在抽屉中等等)可能会导致混乱的用户体验。
- 过度嵌套会导致代码难以理解,例如导航到嵌套屏幕、配置深层链接等。
将嵌套导航器视为实现您想要的用户界面的方式,而不是组织代码的方式。如果您想创建单独的屏幕组以进行组织,而不是使用单独的导航器,可以使用Group
组件。
<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>