7.嵌套导航
简单的案例
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>
);
}
导航嵌套的特性
1.每个导航器保存自己的导航历史
- 例如,当你在嵌套堆栈导航器的屏幕内按下后退按钮时,即使有另一个导航器作为父导航器,它也会回到嵌套堆栈内的上一个屏幕
2.每个导航器都有自己的选项
- 例如,在嵌套在子导航器中的屏幕中指定标题选项不会影响在父导航器中显示的标题
3.导航器中的每个屏幕都有自己的参数
- 例如,任何传递到嵌套导航器中的屏幕的参数都在该屏幕的路由道具中,不能从父或子导航器中的屏幕访问
4.导航操作由当前导航器处理,如果不能处理,则弹出
5.导航器特定的方法可以在嵌套的导航器中使用
6.嵌套导航器不接收父级事件
7.父导航器的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={Settings} />
</Stack.Navigator>
</NavigationContainer>
);
}
在这里,你可能想从Feed组件导航到Root屏幕
navigation.navigate(‘Root’);
现在它会显示root组件内部的初始屏幕,即Home。但有时您可能想要控制应该在导航中显示的屏幕。要实现它,你可以在参数中传递屏幕的名称:
navigation.navigate(‘Root’, { screen: ‘Profile’ });
在嵌套导航器中向屏幕传递参数
navigation.navigate('Root', {
screen: 'Profile',
params: { user: 'jane' },
});
// 深度嵌套的写法
navigation.navigate('Root', {
screen: 'Settings',
params: {
screen: 'Sound',
params: {
screen: 'Media',
},
},
});
呈现导航器中定义的初始路由
默认情况下,当您在嵌套导航器中导航一个屏幕时,指定的屏幕将被用作初始屏幕,导航器上的初始路由道具将被忽略。这个行为不同于React Navigation 4。
如果你需要呈现导航器中指定的初始路由,你可以通过设置initial: false来禁用使用指定屏幕作为初始屏幕的行为:
navigation.navigate('Root', {
screen: 'Settings',
initial: 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>
);
}
嵌套时的最佳实践
我们建议将嵌套导航器减少到最小。尝试用尽可能少的嵌套来实现您想要的行为。嵌套有很多缺点:
- 它导致了嵌套很深的视图层次结构,这可能会导致低端设备的内存和性能问题
- 嵌套同一类型的导航器(例如标签在标签中,抽屉在抽屉中等等)可能会导致用户体验混乱
- 由于嵌套过多,当导航到嵌套屏幕、配置深度链接等时,代码会变得难以理解。
把嵌套导航器看作是实现您想要的UI的一种方法,而不是组织代码的一种方法。如果希望创建用于组织的单独的屏幕组,而不是使用单独的导航器,可以使用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>
8.生命周期
在前面我们学习了如何在屏幕之间导航,重要的问题是:当我们导航离开Home时,或者当我们返回Home时,会发生什么?一个路由如何发现一个用户是离开它还是回到它?
如果你要从一个web后台进行响应式导航,你可以假设当用户从路由a导航到路由B时,a会卸载(它的组件willunmount被调用),当用户返回时,a会再次加载。虽然这些React生命周期方法仍然有效,并且在React导航中使用,但它们的用法与web不同。这是由更复杂的移动导航需求驱动的。
示例场景
一个带有屏幕A和B的本机堆栈导航器。在导航到A之后,它的componentDidMount被调用。当push B时,B的componentDidMount也被调用,但是A仍然挂载在堆栈上,A的componentWillUnmount因此没有被调用。
当从B返回到A时,B的componentWillUnmount被调用,但是A的componentDidMount并不会被调用,因为A一直被挂载。
function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="First">
{() => (
<SettingsStack.Navigator>
<SettingsStack.Screen
name="Settings"
component={SettingsScreen}
/>
<SettingsStack.Screen name="Profile" component={ProfileScreen} />
</SettingsStack.Navigator>
)}
</Tab.Screen>
<Tab.Screen name="Second">
{() => (
<HomeStack.Navigator>
<HomeStack.Screen name="Home" component={HomeScreen} />
<HomeStack.Screen name="Details" component={DetailsScreen} />
</HomeStack.Navigator>
)}
</Tab.Screen>
</Tab.Navigator>
</NavigationContainer>
);
}
我们从HomeScreen开始,然后导航到DetailsScreen。然后我们使用标签栏切换到SettingsScreen,并导航到ProfileScreen。在这一系列操作完成之后,所有4个屏幕都被挂载!如果你使用标签栏切换回HomeStack,你会注意到你会看到一个DetailsScreen - HomeStack的导航状态被保留了!
React导航生命周期事件
现在我们已经理解了React生命周期方法在React Navigation中的工作方式,让我们来回答我们在开始时提出的问题:“我们如何发现用户是离开(blur)它还是回到它(focus)?”
React Navigation向订阅它们的屏幕组件发出事件。我们可以通过倾听focus和blur事件来分别了解屏幕何时聚焦或失焦。
function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// Screen was focused
// Do something
});
return unsubscribe;
}, [navigation]);
return <ProfileContent />;
}
我们可以使用useFocusEffect钩子来执行,而不是手动添加事件监听器。它就像React的useEffect钩子,但它与导航生命周期紧密相连
import { useFocusEffect } from '@react-navigation/native';
function Profile() {
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
};
}, [])
);
return <ProfileContent />;
}
如果你想根据屏幕是否聚焦来呈现不同的东西,你可以使用useIsFocused钩子,它返回一个布尔值,指示屏幕是否聚焦。