本小节主要介绍 React Navigation 中的嵌套路由和路由的生命周期。
嵌套路由#
嵌套路由意味着在一个屏幕内渲染另一个路由,例如:
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 组件内 Stack 导航的主屏幕。所以在这里,选项卡路由嵌套在一个堆栈导航器中,类似于如下的结构:
- Stack.Navigator
- Home (Tab.Navigator)
- Feed (Screen)
- Messages (Screen)
- Profile (Screen)
- Settings (Screen)
- Home (Tab.Navigator)
下面是一个比较常见的嵌套路由示例:
import * as React from 'react'import { Button, View, Text } from 'react-native'import { NavigationContainer } from '@react-navigation/native'import { createNativeStackNavigator } from '@react-navigation/native-stack'import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
function SettingsScreen({ navigation }) { return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Settings Screen</Text> <Button title="Go to Profile" onPress={() => navigation.navigate('Profile')} /> </View> )}
function ProfileScreen({ navigation }) { return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Profile Screen</Text> <Button title="Go to Settings" onPress={() => navigation.navigate('Settings')} /> </View> )}
function HomeScreen({ navigation }) { return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Home Screen</Text> <Button title="Go to Details" onPress={() => navigation.navigate('Details')} /> </View> )}
function DetailsScreen({ navigation }) { return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Details Screen</Text> <Button title="Go to Details... again" onPress={() => navigation.push('Details')} /> </View> )}const Tab = createBottomTabNavigator()const SettingsStack = createNativeStackNavigator()const HomeStack = createNativeStackNavigator()
export default function App() { return ( <NavigationContainer> <Tab.Navigator screenOptions={{ headerShown: false }}> <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> )}当使用嵌套路由时,有一些注意细节,这里可以参阅官方文档:
https://reactnavigation.org/docs/nesting-navigators/#how-nesting-navigators-affects-the-behaviour
嵌套路由的最佳实践
一般来讲,我们应该尽可能的减少嵌套的层数,因为过多的嵌套层数可能会导致如下的问题:
- 过深的嵌套层数可能导致低端设备出现内存和性能问题
- 嵌套相同类型的导航器(例如选项卡内的选项卡,抽屉内的抽屉等)可能会导致混乱的用户体验
- 由于嵌套过多,在导航到嵌套屏幕、配置深层链接等时,代码变得难以调试和阅读
下面是一个关于登录注册的嵌套路由的最佳实践示例:
<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>生命周期#
在 React 的类组件中,存在生命周期这一特性。
考虑具有屏幕 A 和 B 的 Stack 类型路由。导航到 A 后,调用其 componentDidMount。在压入 B 时,它的 componentDidMount 也会被调用,但 A 仍然挂载在堆栈上,因此不会调用它的 componentWillUnmount。
从 B 回到 A 时,调用了 B 的 componentWillUnmount,但 A 的 componentDidMount 没有被调用,因为 A 一直处于挂载状态。
这就是在 React 中类组件的生命周期钩子函数特性。这些 React 生命周期方法在 React Navigation 中仍然有效。
不过自从 React 推出了 Hook 后,更多的使用函数式组件,类组件中的生命周期钩子函数自然也被一些 Hook 替代。
我们可以通过监听 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 * as React from 'react'import { Button, View, Text } from 'react-native'import { NavigationContainer, useFocusEffect } from '@react-navigation/native'import { createNativeStackNavigator } from '@react-navigation/native-stack'import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
function SettingsScreen({ navigation }) { return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Settings Screen</Text> <Button title="Go to Profile" onPress={() => navigation.navigate('Profile')} /> </View> )}
function ProfileScreen({ navigation }) { useFocusEffect( React.useCallback(() => { alert('Screen was focused') // Do something when the screen is focused return () => { alert('Screen was unfocused') // Do something when the screen is unfocused // Useful for cleanup functions } }, []) )
return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Profile Screen</Text> <Button title="Go to Settings" onPress={() => navigation.navigate('Settings')} /> </View> )}
function HomeScreen({ navigation }) { return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Home Screen</Text> <Button title="Go to Details" onPress={() => navigation.navigate('Details')} /> </View> )}
function DetailsScreen({ navigation }) { return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Details Screen</Text> <Button title="Go to Details... again" onPress={() => navigation.push('Details')} /> </View> )}const Tab = createBottomTabNavigator()const SettingsStack = createNativeStackNavigator()const HomeStack = createNativeStackNavigator()
export default function App() { return ( <NavigationContainer> <Tab.Navigator screenOptions={{ headerShown: false }}> <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> )}