react navigation7.x 中文文档青春极速mini版-Fundamentals
0. 术语表
-
Navigator 导航器。导航器是一个 React 组件,决定如何渲染您定义的屏幕。它包含 Screen 元素作为其子元素,以定义屏幕的配置。
-
NavigationContainer 一个组件。用于管理我们的导航树并包含导航状态。此组件必须包装所有导航器结构。通常情况下,我们会在应用程序的根组件中渲染此组件,通常是从 App.js 导出的组件。
-
Router Router 是一组函数,用于决定如何处理导航器中的操作和状态更改(类似于 Redux 应用程序中的 reducer)。通常情况下,您不需要直接与 Router 交互,除非您正在编写自定义导航器。
-
Screen Component 屏幕组件。屏幕组件是我们在路由配置中使用的组件。
const Stack = createNativeStackNavigator();
const StackNavigator = (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen} // <----
/>
<Stack.Screen
name="Details"
component={DetailsScreen} // <----
/>
</Stack.Navigator>
);
组件名称中的后缀 Screen 是完全可选的,但是经常使用的约定;我们可以将其称为 Michael,这仍然可以正常工作。
我们之前看到,我们的屏幕组件会提供 navigation prop。重要的是,只有在屏幕作为路由被 React Navigation 渲染时(例如响应于 navigation.navigate),才会发生这种情况。
例如,如果我们将 DetailsScreen 渲染为 HomeScreen 的子组件,则不会为 DetailsScreen 提供 navigation prop,当您在 Home 屏幕上按下“再次转到详细信息...”按钮时,应用程序将抛出经典的 JavaScript 异常“undefined is not an object”。
function HomeScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => navigation.navigate('Details')}
/>
<DetailsScreen />
</View>
);
}
导航属性参考 部分详细介绍了此问题,描述了解决方法,并提供了有关 navigation prop 上其他可用属性的更多信息。
-
Navigation Prop 导航属性, 此 prop 将传递给所有屏幕,可以用于以下操作:
dispatch将向路由器发送一个 actionnavigate、goBack等可用于方便地调度操作
导航器也可以接受 navigation prop,如果有父级导航器,则应从父级导航器获取它。
有关更多详细信息,请参见导航属性文档。
路由属性参考 部分详细介绍了此问题,描述了解决方法,并提供了有关 route prop 上其他可用属性的更多信息。
-
Route Prop 路由属性
此 prop 将传递给所有屏幕。包含有关当前路由的信息,例如 params、key 和 name。
-
Navigation State 导航状态, 导航器的状态通常如下所示:
{
key: 'StackRouterRoot',
index: 1,
routes: [
{ key: 'A', name: 'Home' },
{ key: 'B', name: 'Profile' },
]
}
对于此导航状态,有两个路由(可以是选项卡或堆栈中的卡片)。索引指示活动路由,即“B”。
您可以在此处阅读有关导航状态的更多信息。
-
Route 路由
每个路由都是一个对象,包含用于标识它的键和用于指定路由类型的“名称”。它还可以包含任意参数。
{
key: 'B',
name: 'Profile',
params: { id: '123' }
}
-
Header 标题
也称为导航标题、导航栏、应用栏,可能还有许多其他名称。这是屏幕顶部的矩形,其中包含返回按钮和屏幕标题。整个矩形通常在 React Navigation 中称为标题。
-
StackNavigator- 为应用程序提供了一种页面切换的方法,每次切换时,新的页面会放置在堆栈的顶部 -
TabNavigator- 用于设置具有多个Tab页的页面 -
DrawerNavigator- 用于设置抽屉导航的页面
1. 开始
1.1 基本要求
react-native>= 0.63.0expo>= 41 (if you use Expo)typescript>= 4.1.0 (if you use TypeScript)
1.2 安装依赖
yarn add @react-navigation/native
yarn add react-native-screens react-native-safe-area-context
可选:使用 expo 创建的项目
npx expo install react-native-screens react-native-safe-area-context
提示:只要程序能够正常构建你可以忽略由 peer dependencies 引起的警告
对于 React Native 0.60 版本或更高,无需再运行 react-native link
对于 Mac 用户 IOS 开发, 需要安装 pods (通过 Cocoapods)以完成链接
npx pod-install ios
对于 Android 开发需要一些额外的配置
编辑 android/app/src/main/java/<your package name>/MainActivity.java 文件
+ import android.os.Bundle;
public class MainActivity extends ReactActivity {
// ...
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(null);
+ }
// ...
}
这一变化是必要的,以避免与视图状态在活动重启时不一致而导致的崩溃。
注意:当你使用一个导航器(如 Stack.Navigator )时,你需要按照该导航器的安装说明来安装任何额外的依赖项。如果报错 "Unable to resolve module",你需要在你的项目中安装该模块。
1.3 使用 NavigationContainer 包裹 app
// App.js
import * as React from 'react'
import { NavigationContainer } from '@react-navigation/native'
export default function App() {
return <NavigationContainer>{/* Rest of your app code */}</NavigationContainer>
}
注意: NavigationContainer 应该只有一个,除非用例特殊,你不应该嵌套它
2. Hello React Navigation
React Native 没有内置类似 web 的的全局历史堆栈概念, React Navigation 提供了这些
React Navigation 带有三种默认的 navigator。
Stack.Navigator- 为应用程序提供了一种页面切换的方法,每次切换时,新的页面会放置在堆栈的顶部Tab.Navigator- 用于设置具有多个 Tab 页的页面Drawer.Navigator- 用于设置抽屉导航的页面
React Navigation 的本地 Stack.Navigator 提供了一种方法,使您的应用程序可以在屏幕之间进行转换并管理导航历史记录。如果您的应用程序仅使用一个 Stack.Navigator ,则在概念上类似于 Web 浏览器处理导航状态 - 您的应用程序在用户与其交互时从导航堆栈中推送和弹出项目,这会导致用户看到不同的屏幕。这在 Web 浏览器和 React Navigation 中的工作方式之间的一个关键区别是,React Navigation 的本地 Stack.Navigator 提供了您在 Android 和 iOS 上导航到堆栈中的路由时所期望的手势和动画。
2.1 安装 @react-navigation/native-stack
到目前为止,我们所安装的库是导航器的构建模块和共享基础,React Navigation 中的每个导航器都在自己的库中。要使用本地 Stack.Navigator ,我们需要安装 @react-navigation/native-stack
yarn add @react-navigation/native-stack
@react-navigation/native-stack依赖于react-native-screens
2.2 createNativeStackNavigator & NavigationContainer
createNativeStackNavigator 函数返回一个包含 Screen 和 Navigator 属性的对象。它们都是用于配置导航器的 React 组件。导航器应该包含屏幕元素作为其子代,以定义路由的配置。
NavigationContainer 是一个管理我们的导航树并包含导航状态的组件。这个组件必须包住所有的导航器结构。通常,我们会在应用程序的根部渲染这个组件,这通常是由 App.js 导出的组件。
// In App.js in a new project
import * as React from 'react'
import { View, Text } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
function HomeScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
</View>
)
}
const Stack = createNativeStackNavigator()
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name='Home' component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
export default App

导航条和内容区的样式是 Stack.Navigator 的默认配置
现在来添加第二个页面
function DetailsScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
</View>
)
}
const Stack = createNativeStackNavigator()
function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'>
<Stack.Screen name='Home' component={HomeScreen} />
<Stack.Screen name='Details' component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
initialRouteName 指定了初始路由
现在我们有两个路由: Home 和 Detail, 路由可以通过 Stack.Screen 组件指定,
name属性对应路由名称,component属性对应将要渲染的组件
注意:组件 props 接受的是组件,而不是渲染函数。不要传递内联函数(例如:
component={() => <HomeScreen />}),否则当父级组件重新渲染时,你的组件会卸载-重新挂载且失去所有的状态。参见传递额外的 props以了解替代方案。
2.3 指定选项
导航器中的每个 Screen 都可以为导航器指定一些选项,比如要在 Header 中呈现的标题。这些选项可以在每个屏幕组件的 props 中传递:
<Stack.Screen name='Home' component={HomeScreen} options={{ title: 'Overview' }} />
如果想要为所有 screen 传递相同的选项,使用 screenOptions
2.4 传递额外的属性
两种方法
- 使用
React Context - 使用 Render Props
<Stack.Screen name='Home'>{props => <HomeScreen {...props} extraData={someData} />}</Stack.Screen>
注意:默认情况下,React Navigation 会对屏幕组件进行优化,以防止不必要的渲染。使用 Render Props 会移除这些优化。因此,如果你使用它,你需要确保你的
Screen组件使用React.memo或React.PureComponent以避免性能问题。
3. 路由跳转
在 web 浏览器中路由跳转的写法可能是
<a href="details.html">Go to Details</a>
或者
<a onClick={() => { window.location.href = 'details.html'; }}>
Go to Details
</a>
而 React Navigation 中则使用传递到 screen 组件中的 navigation 属性
import { Button, View, Text } from 'react-native'
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>
)
}
navigation属性传递到导航器下每个 screen 组件中- 使用
navigation.navigate('Details')跳转
如果跳转一个未定义的路由名称,在开发构建时会打印一个错误,生产构建中什么都不会发生
3.1 navgate & push
function DetailsScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Button title='Go to Details... again' onPress={() => navigation.navigate('Details')} />
</View>
)
}
如果你运行这段代码,你会注意到,当你点击 "Go to Details... again"时,它并没有做任何事情!这是因为我们已经进入了详细信息路线!这是因为我们已经在 Details 路由上了。navigate 的意思是 "去这个屏幕",如果你已经在那个屏幕上,那么它什么也不做也是合理的。
让我们假设一下,我们实际上想添加另一个 Details 界面。这在你向路由传递一些独特的数据的情况下是很常见的(稍后在我们谈论参数时,会有更多关于这个问题的介绍!)。要做到这一点,我们可以把 navigate 改为 push。这允许我们表达添加另一路由的意图,而不考虑现有的导航历史。
<Button title='Go to Details... again' onPress={() => navigation.push('Details')} />
navigate 和 push: 它们的区别在于
- 如果已经加载了页面,navigate 方法将跳转到已经加载的页面,而不会重新创建一个新的页面
- 而 push 方法则会在堆栈顶部添加一条路由,并导航至该路由
3.2 goBack()
当可以从活动屏幕返回时,本地 Stack.Navigator 提供的标题将自动包括一个返回按钮(如果导航堆栈中只有一个屏幕,就没有可以返回的东西,所以没有返回按钮) goBack() 可以编程方式触发返回这一行为
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')} />
<Button title='Go to Home' onPress={() => navigation.navigate('Home')} />
<Button title='Go back' onPress={() => navigation.goBack()} />
</View>
)
}
在安卓系统中,React Navigation 与硬件后退按钮挂钩,当用户按下后退按钮时为你启动
goBack()函数,因此它的行为与用户所期望的一样。
返回堆栈中第一个屏幕
// 回到 Home 页
<Button title='Go to Home' onPress={() => navigation.navigate('Home')} />
// 回到堆栈中第一个屏幕
<Button title='Go back to first screen in stack' onPress={() => navigation.popToTop()} />
4. 传递参数
传递参数需要两个步骤:
- 作为
navigate函数的第二个参数navigation.navigate('RouteName', { /* params */}) - 在你的 screen 组件中读取参数:
route.params
我们建议你传递的参数是可序列化的 JSON。这样,你就可以使用状态持久化,你的 screen 组件将正确实现 Deep Linking。
Deep Linking 是一种优化用户体验的策略,它允许您通过 URL 直接导航到应用程序的特定屏幕。React Native 提供了 Linking 来获取传入链接的通知。React Navigation 可以与 Linking 模块集成,以自动处理深度链接。在 Web 上,React Navigation 可以与浏览器的历史记录 API 集成,以处理客户端上的 URL
示例: 传递
onPress = {() => {
navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
})
}}
示例: 读取
function DetailsScreen({ route, navigation }) {
const { itemId, otherParam } = route.params
// ...
}
4.1 传递初始参数
如果你在导航到这个屏幕时没有指定任何参数,初始参数将被使用。它们也会与你传递的任何参数进行浅层合并。初始参数用一个initialParams 来指定
<Stack.Screen name='Details' component={DetailsScreen} initialParams={{ itemId: 42 }} />
4.2 更新参数
屏幕也可以更新它们的参数,就像它们可以更新它们的状态一样。navigation.setParams 方法让你更新屏幕的参数。更多细节请参考 setParams 的 API 参考。
navigation.setParams({ query: 'someText' })
注意:避免使用
setParams来更新屏幕选项,如 title 等。如果你需要更新选项,请使用setOptions代替。
4.3 将参数传递给前一个屏幕
假设你有一个带有创建帖子按钮的屏幕,而创建帖子按钮打开了一个新的屏幕来创建一个帖子。在创建帖子后,你想把帖子的数据传回给前一个屏幕。
为了实现这一点,你可以使用 navigate 方法,如果屏幕已经存在,它的作用类似于 goBack。你可以通过 navigate 传递参数来把数据传回去:
function HomeScreen({ navigation, route }) {
React.useEffect(() => {
if (route.params?.post) {
// Post updated, do something with `route.params.post`
// For example, send the post to the server
}
}, [route.params?.post])
return (
<View>
<Button title='Create post' onPress={() => navigation.navigate('CreatePost')} />
<Text style={{ margin: 10 }}>Post: {route.params?.post}</Text>
</View>
)
}
function CreatePostScreen({ navigation, route }) {
const [postText, setPostText] = React.useState('')
return (
<>
<TextInput
multiline
placeholder="What's on your mind?"
style={{ height: 200, padding: 10, backgroundColor: 'white' }}
value={postText}
onChangeText={setPostText}
/>
<Button
title='Done'
onPress={() => {
// Pass and merge params back to home screen
navigation.navigate({
name: 'Home',
params: { post: postText },
merge: true,
})
}}
/>
</>
)
}
4.4 将参数传递给嵌套的导航器
例如,假设你在 Account 屏幕内有一个导航器,并想把参数传给该导航器内的 Settings 屏幕。那么你可以按以下方式传递参数:
navigation.navigate('Account', {
screen: 'Settings',
params: { user: 'jane' },
})
4.5 参数中应该有什么
参数就像屏幕的选项。它们应该只包含配置屏幕中显示的信息。避免传递将在屏幕上显示的全部数据(例如,传递一个用户 ID 而不是用户对象)。也要避免传递被多个屏幕使用的数据,这种数据应该在一个全局存储中。
你也可以把路由对象看成是一个 URL。如果你的屏幕有一个 URL,那么 URL 中应该有什么?参数不应该包含那些你认为不应该出现在 URL 中的数据。这通常意味着你应该尽可能少地保留确定屏幕是什么所需的数据。想想访问一个购物网站,当你看到产品列表时,URL 通常包含类别名称、排序类型、过滤器等,它不包含屏幕上显示的实际产品列表。
例如,如果你有一个简介屏幕。当导航到它时,你可能会想在参数中传递用户对象:
// Don't do this
navigation.navigate('Profile', {
user: {
id: 'jane',
firstName: 'Jane',
lastName: 'Done',
age: 25,
},
})
这看起来很方便,让你用 route.params.user 访问用户对象,而不需要任何额外的工作。
然而,这是个反模式的做法。像用户对象这样的数据应该在你的全局存储中,而不是在导航状态中。否则,你就会在多个地方重复相同的数据。这可能会导致一些错误,比如即使用户对象在导航后发生了变化,配置文件屏幕也会显示过时的数据。
通过 DeepLink 或在网络上链接到屏幕也成为问题,因为:
- URL 是屏幕的代表,所以它也需要包含参数,即完整的用户对象,这可能会使 URL 变得非常长且不可读
- 由于用户对象在 URL 中,所以有可能传递一个随机的用户对象,代表一个不存在的用户,或者在配置文件中的数据不正确的用户
- 如果没有传递用户对象,或者格式不正确,这可能会导致崩溃,因为屏幕不知道如何处理它。
一个更好的方法是在 params 中只传递用户的 ID:
navigation.navigate('Profile', { userId: 'jane' })
实质上,在 params 中传递识别屏幕所需的最少的数据,在很多情况下,这仅仅意味着传递一个对象的 ID 而不是传递一个完整的对象。将你的应用程序数据与导航状态分开。
5. 配置 Header
5.1 配置 title
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen name='Home' component={HomeScreen} options={{ title: 'My home' }} />
</Stack.Navigator>
)
}
5.2 在 title 中使用 params
为了在标题中使用 params,我们需要让屏幕的 options 成为一个返回配置对象的函数。
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen name='Home' component={HomeScreen} options={{ title: 'My home' }} />
<Stack.Screen name='Profile' component={ProfileScreen} options={({ route }) => ({ title: route.params.name })} />
</Stack.Navigator>
)
}
试图在 options 里面使用 this.props 可能是很诱人的,但是因为它是在组件渲染之前定义的,这并不是指组件的一个实例,因此没有 props 可用。相反,如果我们把 options 变成一个函数,那么 React Navigation 会用一个包含 { navigation, route } 的对象来调用它。- 在这种情况下,我们所关心的是路由,它是作为路由 props 传递给你的屏幕 props 的同一个对象。你可能记得,我们可以通过 route.params 获得参数
传入 options 函数的参数是一个具有以下属性的对象:
navigationroute
5.3 使用 setOptions 更新 options
/* Inside of render() of React class */
<Button title='Update the title' onPress={() => navigation.setOptions({ title: 'Updated!' })} />
5.4 调整 header 样式
3 个属性调整 header 样式:
headerStyle:一个样式对象,它将被应用到包裹 Header 的View中。headerTintColor:后退按钮和标题都使用这个属性作为其颜色。headerTitleStyle:如果我们想为标题定制 fontFamily、fontWeight 和其他文本样式属性,我们可以用这个来做。
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name='Home'
component={HomeScreen}
options={{
title: 'My home',
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
/>
</Stack.Navigator>
)
}

这里有几件事情需要注意:
- 在 iOS 上,状态栏的文字和图标都是黑色的,这在深色的背景上看起来并不好看。我们不会在这里讨论这个问题,但你应该确保按照状态栏指南中的描述,将状态栏配置为适合你的屏幕颜色。
- 我们设置的配置只适用于主屏幕;当我们导航到细节屏幕时,就恢复了默认的样式。我们现在来看看如何在屏幕之间共享选项。
5.5 在屏幕间共享 options
将配置移到 Stack.Navigator 的 screenOptions 属性下 。
function StackScreen() {
return (
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}>
<Stack.Screen name='Home' component={HomeScreen} options={{ title: 'My home' }} />
</Stack.Navigator>
)
}
5.6 覆盖 title 组件
function LogoTitle() {
return <Image style={{ width: 50, height: 50 }} source={require('@expo/snack-static/react-native-logo.png')} />
}
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen name='Home' component={HomeScreen} options={{ headerTitle: props => <LogoTitle {...props} /> }} />
</Stack.Navigator>
)
}
你可能想知道,当我们提供一个组件而不是像以前那样提供 title,为什么是
headerTitle?原因是headerTitle是一个特定于 Stack.Navigator 的属性,headerTitle默认为一个显示 title 的Text组件。
完整列表
5.7 添加一个 button
与 Header 互动的最常见方式是点击标题左边或右边的按钮。让我们在标题的右侧添加一个按钮(在整个屏幕上最难触摸的地方之一,取决于手指和手机的大小,但也是一个正常的放置按钮的地方)。
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name='Home'
component={HomeScreen}
options={{
headerTitle: props => <LogoTitle {...props} />,
headerRight: () => <Button onPress={() => alert('This is a button!')} title='Info' color='#fff' />,
}}
/>
</Stack.Navigator>
)
}
当我们这样定义我们的按钮时,options 中的 this 变量不是 HomeScreen 实例,所以你不能对它调用 setState 或任何实例方法。这一点非常重要,因为希望你的 Header 中的按钮与 Header 所属的屏幕进行交互是非常常见的。所以,我们接下来会看看如何做到这一点。
5.8 Header 与其屏幕组件的互动
function HomeScreen({ navigation }) {
const [count, setCount] = React.useState(0)
React.useEffect(() => {
navigation.setOptions({
headerRight: () => <Button onPress={() => setCount(c => c + 1)} title='Update count' />,
})
}, [navigation])
return <Text>Count: {count}</Text>
}
在这里,我们用一个带有 onPress 处理程序的按钮来更新 headerRight ,这个处理程序可以访问组件的状态,并可以更新它。
5.9 自定义返回 button
createNativeStackNavigator 为返回按钮提供了特定平台的默认值。在 iOS 上,这包括按钮旁边的一个标签,当标题符合可用空间时,它显示前一个屏幕的标题,否则它显示 "返回"。
你可以用 headerBackTitle 改变标签行为,用 headerBackTitleStyle 改变其样式(阅读更多)。
要自定义返回按钮的图像,你可以使用 headerBackImageSource (阅读更多)。
5.10 覆盖返回 button
只要用户有可能从当前屏幕返回,后退按钮就会在 Stack.Navigator 中自动呈现--换句话说,只要堆栈中存在多个屏幕,后退按钮就会呈现。
一般来说,这是你想要的。但在某些情况下,你有可能想通过上面提到的选项对后退按钮进行更多的定制,在这种情况下,你可以将 headerLeft 选项设置为一个将被渲染的 React 元素,就像我们对 headerRight 所做的一样。另外,headerLeft 选项也可以接受一个 React 组件,例如,它可以用来覆盖后退按钮的 onPress 行为。在api 参考中阅读更多关于这个的内容。
6. 嵌套导航
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>
)
}
上边的代码结构
Stack.NavigatorHome(Tab.Navigator)Feed(Screen)Messages(Screen)
Profile(Screen)Settings(Screen)
6.1 嵌套导航器注意事项
当使用嵌套导航器时,有一些事情需要记住:
-
每个导航器都保留自己的导航历史
例如,当你在一个嵌套 Stack.Navigator 的屏幕内按下后退按钮时,它会回到嵌套堆栈内的前一个屏幕,即使有另一个导航器作为父级。
-
每个导航器都有自己的 options
例如,在一个嵌套在子导航器中的屏幕中指定一个标题选项,不会影响在父导航器中显示的标题。
如果你想实现这种行为,请看嵌套导航器的屏幕选项指南。如果你在一个 Stack.Navigator 中渲染一个 TabNavigator ,并想在 StackNavigator 的 Header 中显示标签导航器中的活动屏幕的标题,这可能很有用。
-
一个导航器中的每个屏幕都有自己的参数
例如,传递给嵌套导航器中的屏幕的任何参数都在该屏幕的路由 props 中,不能从父级或子级导航器的屏幕中访问。
如果你需要从子屏幕上访问父屏幕的参数,你可以使用
React Context将参数暴露给子屏幕。 -
导航操作由当前导航器处理,如果无法处理则会冒泡上升。
例如,在嵌套的屏幕中调用
navigation.goBack(),如果您已经在导航器的第一个屏幕上,则仅会在父导航器中返回。其他操作(如navigate)也类似,即在嵌套导航器中进行导航,如果嵌套导航器无法处理,则父导航器将尝试处理。在上面的示例中,当在 Feed 屏幕中调用
navigate('Messages')时,嵌套的Tab.Navigator将处理它,但如果调用navigate('Settings'),则父Stack.Navigator将处理它。 -
嵌套的导航器中可用的导航器特定方法
例如,如果在 DrawerNavigator 中有一个 StackNavigator,那么在屏幕中的
navigationprop 上也将可用 Drawer.Navigator 的openDrawer、closeDrawer、toggleDrawer等方法。但是,如果您有一个 StackNavigator 作为 DrawerNavigator 的父级,则 StackNavigator 内的屏幕将无法访问这些方法,因为它们没有嵌套在 DrawerNavigator 中。同样,如果您在 StackNavigator 内有一个 TabNavigator,则 TabNavigator 中的屏幕将在其
navigationprop 中获取 StackNavigator 的push和replace方法。如果您需要从父级向嵌套的子导航器发送操作,则可以使用
navigation.dispatch:
navigation.dispatch(DrawerActions.toggleDrawer())
-
嵌套的导航器不接收父级的事件
例如,如果你有一个嵌套在 TabNavigator 内的 StackNavigator,当使用
navigation.addListener时,StackNavigator 中的屏幕不会收到父 TabNavigator 发出的事件,例如(tabPress)。为了接收来自父级导航器的事件,你可以用
navigation.getParent明确地监听父级的事件:
const unsubscribe = navigation
.getParent('MyTabs')
.addListener('tabPress', (e) => {
// Do something
});
这里 'MyTabs' 指的是你在父级 Tab.Navigator 的id props 中传递的值,你想监听的是这个事件。
-
父级导航器的 UI 渲染在子级导航器之上
例如,当您在 Drawer.Navigator 中嵌套一个 Stack.Navigator 时,您会发现 Drawer.Navigator 出现在 Stack.Navigator 的标题栏之上。然而,如果您将 Drawer.Navigator 嵌套在 Stack.Navigator 中,则 Drawer.Navigator 将出现在堆栈的标题栏之下。在决定如何嵌套您的导航器时,这是一个重要的考虑点。
在您的应用程序中,您可能会根据所需的行为使用以下模式:
- 在 Stack.Navigator 的初始屏幕中嵌套 Tab.Navigator - 新屏幕在推送时覆盖选项卡栏。
- 在 Stack.Navigator 的初始屏幕中嵌套 Drawer.Navigator ,并隐藏初始屏幕的堆栈标题 - 抽屉只能从堆栈的第一个屏幕打开。
- 在 Drawer.Navigator 的每个屏幕中嵌套 Stack.Navigator - 抽屉从堆栈的标题栏上方出现。
- 在 Tab.Navigator 的每个屏幕中嵌套 Stack.Navigator - 选项卡栏始终可见。通常再次按选项卡会将堆栈弹回到顶部。
6.2 在一个嵌套的导航器中导航到一个屏幕
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 组件内的初始屏幕,也就是主页。但有时你可能想控制导航时应显示的屏幕。为了实现这一点,你可以在 params 中传递屏幕的名称:
navigation.navigate('Root', { screen: 'Profile' });
现在,导航时将呈现 Profile 屏幕而不是 Home。
这可能看起来与之前导航在嵌套屏幕上的工作方式有很大不同。不同的是,在以前的版本中,所有的配置都是静态的,所以 `React Navigation 可以通过遍历嵌套配置来静态地找到所有导航器及其屏幕的列表。但在动态配置下,React Navigation在包含屏幕的导航器渲染之前,并不知道哪些屏幕是可用的,在哪里。通常情况下,一个屏幕在你导航到它之前不会渲染其内容,所以还没有渲染的导航器的配置还不可用。这使得你有必要指定你要导航的层次结构。这也是为什么你应该尽可能地减少导航器的嵌套,以保持你的代码更简单。
6.3 在嵌套导航器中向屏幕传递参数
navigation.navigate('Root', {
screen: 'Profile',
params: { user: 'jane' },
});
如果导航器已经被渲染了,在 StackNavigator 的情况下,导航到另一个屏幕将推送一个新的屏幕。
对于深度嵌套的屏幕,你可以遵循类似的方法。注意,这里导航的第二个参数只是 params,所以你可以做这样的事情:
navigation.navigate('Root', {
screen: 'Settings',
params: {
screen: 'Sound',
params: {
screen: 'Media',
},
},
});
在上面的例子中,你正在导航到 Media 屏幕,它在一个导航器中,嵌套在 Sound 屏幕中,而 Sound 在一个导航器中,嵌套在 Settings 屏幕中。
6.4 嵌套多个导航器
嵌套多个导航器,如 stack、drawer 或 tabs,有时是很有用的。
当嵌套多个stack、drawer 或底部tabs 导航器时,子导航器和父导航器的标题都会显示。然而,通常情况下,在子导航器中显示标题,而在父导航器的屏幕中隐藏标题是更可取的。
为了实现这一点,你可以使用 headerShown: false 选项在包含导航器的屏幕中隐藏 Header。
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>
);
}
在这些例子中,我们使用了一个直接嵌套在另一个 StackNavigator 内的底部 TabNavigator,但是当中间有其他导航器时,同样的原则也适用,例如:StackNavigator 在一个 TabNavigator内,而这个 TabNavigator在另一个 StackNavigator内,StackNavigator在 DrawerNavigator内等等。
如果你不希望任何一个导航器中出现标题,你可以在所有的导航器中指定 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>
);
}
6.5 嵌套导航的最佳实践
我们建议尽量减少导航器的嵌套。尽可能少地嵌套导航器来实现您想要的行为。嵌套有许多缺点:
- 它会导致深度嵌套的视图层次结构,可能会在低端设备上引起内存和性能问题。
- 嵌套相同类型的导航器(例如选项卡内嵌选项卡,抽屉内嵌抽屉等)可能会导致令人困惑的用户体验。
- 如果嵌套过度,当导航到嵌套屏幕、配置深度链接等时,代码会变得难以跟踪。
将导航器嵌套视为实现所需 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>
7. 导航生命周期
一个问题:当我们从 Home 导航到其他页面时,或者当我们回到 Home 页面时,Home 页面会发生什么?一个路由如何知道用户正在离开它或回到它?
如果您是从 Web 背景转入 react-navigation,您可能会认为当用户从路由 A 导航到路由 B 时,A 将卸载(其 componentWillUnmount 被调用),并且当用户回到 A 时 A 将重新挂载。虽然这些 React 生命周期方法仍然有效并且在 react-navigation 中使用,但它们的用法与 Web 不同。这是由移动导航的更复杂需求驱动的。
示例场景
考虑一个包含屏幕 A 和 B 的堆栈式导航器。导航到 A 后,将调用其 componentDidMount。当推入 B 时,也会调用其 componentDidMount,但是 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 的导航状态已被保留!
7.1 React Navigation 生命周期事件
现在我们了解了 React 生命周期方法在 React Navigation 中的工作方式,让我们回答我们一开始提出的问题:“我们如何知道用户何时离开(失焦)或返回(聚焦)屏幕?”
React Navigation 向订阅它们的屏幕组件发出事件。我们可以监听 focus 和 blur 事件,以分别了解屏幕何时进入焦点或离开焦点。
示例:
function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// Screen was focused
// Do something
});
return unsubscribe;
}, [navigation]);
return <ProfileContent />;
}
有关可用事件和 API 使用的更多详细信息,请参见 Navigation 事件。
我们可以使用 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 钩子,该钩子返回一个布尔值,指示屏幕是否聚焦。
转载自:https://juejin.cn/post/7244075109442601015