Skip to content

嵌套路由与生命周期

本小节主要介绍 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 导航的主屏幕。所以在这里,选项卡路由嵌套在一个堆栈导航器中,类似于如下的结构:

下面是一个比较常见的嵌套路由示例:

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 的类组件中,存在生命周期这一特性。

考虑具有屏幕 ABStack 类型路由。导航到 A 后,调用其 componentDidMount。在压入 B 时,它的 componentDidMount 也会被调用,但 A 仍然挂载在堆栈上,因此不会调用它的 componentWillUnmount

B 回到 A 时,调用了 BcomponentWillUnmount,但 AcomponentDidMount 没有被调用,因为 A 一直处于挂载状态。

这就是在 React 中类组件的生命周期钩子函数特性。这些 React 生命周期方法在 React Navigation 中仍然有效。

不过自从 React 推出了 Hook 后,更多的使用函数式组件,类组件中的生命周期钩子函数自然也被一些 Hook 替代。

我们可以通过监听 focusblur 事件来分别了解屏幕何时聚焦或失焦。

function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// Screen was focused
// Do something
})
return unsubscribe
}, [navigation])
return <ProfileContent />
}

另外,我们还可以使用 useFocusEffect 挂钩来执行副作用来替代上面手动添加事件侦听器的方式。它类似于 ReactuseEffect 钩子,但它与导航生命周期相关联。

下面是一个使用示例:

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>
)
}