React Navigation 6.x 的使用
作为 react native
小菜鸡一枚,第一次接触 react navigation 路由框架,对其也是很懵逼,在不断探索学习中;由于平时都是用 Vue
进行开发,所以对 vue-router 比较熟悉一些,故而想着用 vue-router
路由表那一套用法来使用 react navigation
,当然这个用法不是必须和唯一的,你可以根据你的习惯来开发即可。
Let’s go, Just do it!!!
当前环境
- react 18.0.0
- react-native 0.69.3
- @react-navigation/native 6.0.11
- @react-navigation/native-stack 6.7.0
- @react-navigation/bottom-tabs 6.3.2
- @react-navigation/drawer 6.4.3
安装
-
$ npx react-native init ReactNavigationExample --template react-native-template-typescript
-
$ yarn add @react-navigation/native $ yarn add react-native-screens react-native-safe-area-context
-
$ yarn add @react-navigation/native-stack
-
$ yarn add @react-navigation/bottom-tabs
安装 Drawer Navigator(侧边栏菜单)以及相关依赖库
$ yarn add @react-navigation/drawer $ yarn add react-native-gesture-handler react-native-reanimated
从 React Native 0.60 及更高版本开始,Link是自动的,所以你不需要运行
react-native link
命令。
react-native-screens
库需要做一些修改才能在 Android 设备上正常运行,编辑 android/app/src/main/java/<your package name>/MainActivity.java
文件:
1、导入包
import android.os.Bundle;
2、重写 onCreate 方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
}
对于 iOS 项目,别忘了通过 CocoaPods 安装相关依赖:
$ cd ios && pod install && cd ..
使用示例
功能目标
App 底部有一个 TabBar 管理着 首页
和 我的
两个页面,然后还提供了通过 push
方式打开 个人中心
、设置页面
这两个页面,对了,还提供了一个模态视图的用例。En…, 大改就像下面这样👇:
App
├── TabBar
│ ├── 首页
│ └── 我的
├── 个人中心
├── 设置页面
└── 版本更新的模态视图
直接上图看效果:
目录结构
在根目录下新建一个 src
文件夹用于存放我们的代码,新建 src/routes
目录用于存放路由表配置,新建 src/screen
目录用于存放所有页面(控制器),新建 src/@types
目录存放 TypeScript 的 *.d.ts
声明文件,此时目录结构如下所示:
ReactNavigationExample
├── App.tsx
├── Gemfile
├── android
├── app.json
├── babel.config.js
├── index.js
├── ios
├── metro.config.js
├── package.json
├── src
│ ├── @types
│ │ └── navigation.d.ts
│ ├── routes
│ │ └── index.tsx
│ └── screen
│ ├── HomeScreen.tsx
│ ├── ProfileScreen.tsx
│ ├── SettingScreen.tsx
│ ├── TabScreen.tsx
│ ├── UserCenterScreen.tsx
│ └── UpgradeModalScreen.tsx
├── tsconfig.json
└── yarn.lock
根据目录结构把 src
下的文件都创建好。
源码
navigation.d.ts
该文件内容如下:
/** 个人中心页面跳转时的路由携带参数 */
declare type UserCenterRouteParam = { userid: string | number } | undefined
/**
* 定义App提供的所有页面路由名称以及参数
* 其中 key 为路由跳转时的名称, value 为路由跳转时携带的参数
*/
declare type AppParamList = {
Tab: object | undefined
Home: object | undefined
Profile: object | undefined
Setting: object | undefined
UserCenter: UserCenterRouteParam
Upgrade: object | undefined
}
该文件主要是用于定义 App 中路由相关的参数,其中 AppParamList
定义的是所有页面的路由名称以及携带的参数数据,比如打开页面:
navigation.push('这里就是 AppParamList 的Key', {/* 这里就是 AppParamList 中 Key 所对应的值类型 */})
// 比如跳转个人中心页面:navigation.push('UserCenter', { userid: 100001 })
而我们获取页面传递过来的参数时:
// 这里的 params 就是 AppParamList 中 Key 所对应的值类型
// 比如个人中心页面, 这里 params 的数据类型就是 UserCenterRouteParam
const params = route.params
HomeScreen.tsx
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import React, { useLayoutEffect } from 'react'
import { Button, Text, View } from 'react-native'
const HomeScreen: React.FC<NativeStackScreenProps<AppParamList, 'Home'>> = props => {
const { navigation, route } = props
useLayoutEffect(() => {
navigation.setOptions({
headerTitle: `首页${Math.ceil(Math.random() * 100)}`,
headerLeft: () => <Button title="个人中心" onPress={() => props.navigation.push('UserCenter', { userid: 10002 })} />,
headerRight: () => <Button title="设置" onPress={() => props.navigation.push('Setting')} />
})
}, [navigation, route])
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>这里是首页</Text>
<Button title="模态视图" onPress={() => props.navigation.navigate('Upgrade')} />
<Button title="我的" onPress={() => props.navigation.navigate('Profile')} />
</View>
)
}
export default HomeScreen
ProfileScreen.tsx
import React from 'react'
import { Text, View } from 'react-native'
const ProfileScreen: React.FC<{}> = props => {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>我的</Text>
</View>
)
}
export default ProfileScreen
TabScreen.tsx
import React from 'react'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import HomeScreen from './HomeScreen'
import ProfileScreen from './ProfileScreen'
const Tab = createBottomTabNavigator()
const TabScreen: React.FC<{}> = props => {
return (
<Tab.Navigator
screenOptions={{
headerStyle: {
backgroundColor: 'orange'
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold'
}
}}>
<Tab.Screen name="Home" component={HomeScreen} options={{ title: '首页', tabBarLabel: '首页' }} />
<Tab.Screen name="Profile" component={ProfileScreen} options={{ title: '我的', tabBarLabel: '我的' }} />
</Tab.Navigator>
)
}
export default TabScreen
UserCenterScreen.tsx
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import React from 'react'
import { Text, View, Button } from 'react-native'
const UserCenterScreen: React.FC<NativeStackScreenProps<AppParamList, 'UserCenter'>> = props => {
const userid = props.route.params?.userid
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>个人中心页面</Text>
<Text>传递过来的userid:{userid}</Text>
<Button title="个人中心" onPress={() => props.navigation.push('UserCenter', { userid: Math.ceil(Math.random() * 10000000) })} />
<Button title="设置" onPress={() => props.navigation.navigate('Setting')} />
</View>
)
}
export default UserCenterScreen
SettingScreen.tsx
import React from 'react'
import { Text, View } from 'react-native'
const SettingScreen: React.FC<{}> = props => {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>设置</Text>
</View>
)
}
export default SettingScreen
UpgradeModalScreen.tsx
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import React from 'react'
import { Text, TouchableOpacity, View } from 'react-native'
const UpgradeModalScreen: React.FC<NativeStackScreenProps<AppParamList, 'Upgrade'>> = props => {
return (
<TouchableOpacity style={{ flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: '#00000066' }} onPress={() => props.navigation.goBack()}>
<View style={{ width: 300, height: 200, backgroundColor: '#fff', borderRadius: 5 }}>
<Text style={{ fontSize: 16, fontWeight: '700' }}>发现新版本</Text>
<Text style={{ fontSize: 14, color: '#999' }}>发现新版本,快来更新吧!!!</Text>
</View>
</TouchableOpacity>
)
}
export default UpgradeModalScreen
routes/index.ts
接下来就是最关键的路由表配置信息了,先上代码:
import React from 'react'
import { RouteConfig, NavigationState, NavigationContainer, EventMapBase } from '@react-navigation/native'
import { createNativeStackNavigator, NativeStackNavigationOptions } from '@react-navigation/native-stack'
import TabScreen from '../screen/TabScreen'
import UserCenterScreen from '../screen/UserCenterScreen'
import SettingScreen from '../screen/SettingScreen'
import HomeScreen from '../screen/HomeScreen'
import ProfileScreen from '../screen/ProfileScreen'
import UpgradeModalScreen from '../screen/UpgradeModalScreen'
export type AppRouteConfig = RouteConfig<AppParamList, keyof AppParamList, NavigationState, NativeStackNavigationOptions, EventMapBase>
const routes: AppRouteConfig[] = [
{
name: 'Tab',
getComponent: () => TabScreen,
options: {
headerShown: false
}
},
{
name: 'UserCenter',
component: UserCenterScreen,
options: {
title: '个人中心'
}
},
{
name: 'Setting',
component: SettingScreen
},
{
name: 'Upgrade',
component: UpgradeModalScreen,
options: {
headerShown: false,
presentation: 'transparentModal',
animation: 'fade'
}
},
{
name: 'Home',
component: HomeScreen
},
{
name: 'Profile',
component: ProfileScreen
}
]
const Stack = createNativeStackNavigator()
const AppNavigation = () => {
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: 'orange'
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold'
},
headerBackTitle: ''
}}>
{routes.map(route =>
route.component ? (
<Stack.Screen name={route.name} component={route.component} options={route.options} key={route.name} />
) : (
<Stack.Screen name={route.name} getComponent={route.getComponent!} options={route.options} key={route.name} />
)
)}
</Stack.Navigator>
</NavigationContainer>
)
}
export default AppNavigation
我们定义了 AppRouteConfig
用来表示路由配置信息的数据类型,通过定义 routes
变量来存储所有路由配置信息,最后通过导出 AppNavigation
函数组件,在适当的时机将路由表配置转换成对应的 Stack.Screen
屏幕并显示出来。
不管是 栈(Stack)
还是 (模态)Modal
等样式,都可以通过配置 options
中的属性来控制,灵活使用 options
可以实现各种各样的效果。
App.ts
经过了这么多的配置,不要忘了在 App.ts
中调用 AppNavigation
来显示我们的页面。
import React from 'react'
import AppNavigation from './src/routes'
const App: React.FC<{}> = () => {
return <AppNavigation />
}
export default App
可能出现的错误
如果你安装了 react-native-reanimated
库,并且在运行过程中报如下错误:
error: node_modules/react-native-reanimated/src/index.ts: /xxx/ReactNavigationExample/node_modules/react-native-reanimated/src/index.ts: Export namespace should be first transformed by `@babel/plugin-proposal-export-namespace-from`.
5 | export * from './reanimated1';
6 | export * from './reanimated2';
> 7 | export * as default from './Animated';
需要修改 babel.config.js
文件,具体可以参考官方文档 的说明:
module.exports = {
plugins: [
'react-native-reanimated/plugin',
],
};