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

带有嵌套导航器的屏幕选项

在本文档中,我们将解释当存在多个导航器时,屏幕选项是如何工作的。理解这一点非常重要,这样你才能将你的选项放在正确的位置并正确配置你的导航器。如果你把它们放在错误的位置,最好的情况是没有任何事情发生,最坏的情况是会发生令人困惑和意想不到的事情。

你只能从导航器的屏幕组件之一修改该导航器的导航选项。这同样适用于作为屏幕嵌套的导航器。

让我们以一个标签导航器为例,它在每个标签中都包含一个原生堆栈。如果我们在堆栈内部的屏幕上设置选项会发生什么?

const HomeStackScreen = createNativeStackNavigator({
screens: {
A: {
screen: A,
options: {
tabBarLabel: 'Home',
},
},
},
});

const SettingsStackScreen = createNativeStackNavigator({
screens: {
B: {
screen: B,
options: {
tabBarLabel: 'Settings!',
},
},
},
});

const Tab = createBottomTabNavigator({
screens: {
Home: HomeStackScreen,
Settings: SettingsStackScreen,
},
});
在 Snack 上尝试

正如我们之前提到的,你只能从导航器的屏幕组件之一修改该导航器的导航选项。上面的 A 和 B 分别是 HomeStack 和 SettingsStack 中的屏幕组件,而不是标签导航器中的屏幕组件。因此,结果是 tabBarLabel 属性不会应用于标签导航器。不过,我们可以修复这个问题!

const Tab = createBottomTabNavigator({
screens: {
Home: {
screen: HomeStackScreen,
options: {
tabBarLabel: 'Home!',
},
},
Settings: {
screen: SettingsStackScreen,
options: {
tabBarLabel: 'Settings!',
},
},
},
});

const Navigation = createStaticNavigation(Tab);

export default function App() {
return <Navigation />;
}

在 Snack 上尝试

当我们将选项直接设置在包含 HomeStack 和 SettingsStack 组件的 Screen 组件上时,这允许我们在将其用作屏幕组件时控制其父导航器的选项。在这种情况下,我们堆栈组件上的选项配置了标签导航器中渲染堆栈的标签。

基于子导航器状态设置父屏幕选项

想象以下配置

const HomeTabs = createBottomTabNavigator({
screens: {
Feed: FeedScreen,
Profile: ProfileScreen,
Account: AccountScreen,
},
});

const RootStack = createNativeStackNavigator({
screens: {
Home: HomeTabs,
Settings: SettingsScreen,
},
});

const Navigation = createStaticNavigation(RootStack);

export default function App() {
return <Navigation />;
}
在 Snack 上尝试

如果我们尝试为 FeedScreen 设置带有 options 的 headerTitle,这将不起作用。这是因为 App 堆栈只会查看其直接子组件进行配置:HomeTabs 和 SettingsScreen。

但是我们可以使用 getFocusedRouteNameFromRoute 辅助函数,基于我们的标签导航器的导航状态来确定 headerTitle 选项。让我们首先创建一个函数来获取标题

import { getFocusedRouteNameFromRoute } from '@react-navigation/native';

function getHeaderTitle(route) {
// If the focused route is not found, we need to assume it's the initial screen
// This can happen during if there hasn't been any navigation inside the screen
// In our case, it's "Feed" as that's the first screen inside the navigator
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Feed';

switch (routeName) {
case 'Feed':
return 'News feed';
case 'Profile':
return 'My profile';
case 'Account':
return 'My account';
}
}

然后我们可以将此函数与 Screen 组件上的 options 属性一起使用

const RootStack = createNativeStackNavigator({
screens: {
Home: {
screen: HomeTabs,
options: ({ route }) => ({
headerTitle: getHeaderTitle(route),
}),
},
Settings: SettingsScreen,
},
});
在 Snack 上尝试

那么这里发生了什么?借助 getFocusedRouteNameFromRoute 辅助函数,我们可以从这个子导航器(在本例中是标签导航器,因为这是我们正在渲染的)获取当前活动的路由名称,并为标题设置适当的标题。

当你想基于子导航器的状态为父导航器设置选项时,可以使用这种方法。常见的用例包括

  1. 在堆栈标题中显示标签标题:一个堆栈包含一个标签导航器,你想在堆栈标题上设置标题(上面的例子)
  2. 显示没有标签栏的屏幕:一个标签导航器包含一个堆栈,你想在特定屏幕上隐藏标签栏(不推荐,请参阅 隐藏特定屏幕中的标签栏 代替)
  3. 在某些屏幕上锁定抽屉:一个抽屉内部有一个堆栈,你想在某些屏幕上锁定抽屉

在许多情况下,通过重新组织我们的导航器可以实现类似的行为。如果它适合你的用例,我们通常推荐这个选项。

例如,对于上述用例,与其在堆栈导航器内部添加标签导航器,不如在每个标签内部添加一个堆栈导航器。

const FeedStackScreen = createNativeStackNavigator({
screens: {
Feed: FeedScreen,
/* other screens */
},
});

const ProfileStackScreen = createNativeStackNavigator({
screens: {
Profile: ProfileScreen,
/* other screens */
},
});

const HomeTabs = createBottomTabNavigator({
screens: {
Feed: FeedStackScreen,
Profile: ProfileStackScreen,
},
});

const RootStack = createNativeStackNavigator({
screens: {
Home: HomeTabs,
Settings: SettingsScreen,
},
});

const Navigation = createStaticNavigation(RootStack);

export default function App() {
return <Navigation />;
}
在 Snack 上尝试

此外,这允许你通过向这些堆栈添加更多路由,将新屏幕推送到 feed 和 profile 堆栈,而无需隐藏标签栏。

如果你想在标签栏之上推送屏幕(即不显示标签栏的屏幕),那么你可以将它们添加到 App 堆栈,而不是将它们添加到标签导航器内部的屏幕中。