React Navigation 4x到5x的迁移指南
提示:本教程不定期更新,请关注课程章节列表及时获取更新
在这个教程中将向大家分享React Navigation 4x到5x的迁移指南,本教程主要以课程源码为例讲解4x到5x迁移过程中所需要做的事情以及一些经验和心得。
如果你的项目还没有使用4x版本,那么可以直接参考下面教程按照和使用4x或5x版本的导航器:
目录
- 热门问答
- 迁移指南
- 包依赖迁移
- Navigation Container迁移
- 路由配置迁移
- 关于navigation prop的不同
- 不支持Switch Navigator
- 如何将本课程项目
Github_RN
从4x迁移都5x? - 5x还不支持的功能
已经迁移到5x后的课程源码:
热门问答
- 5x相比4x有哪些优缺点?
- 答:5x相比4x最大的优点是在使用的写法上支持了JSX的写法,但相比比较成熟的4x缺点还比是较明显的,主要体现在4x所支持的一些API或功能在5x还没有完全支持;另外因为5x新发布不久,bug会比4x多一些;还有就是5x生态没有4x健全,用的公司也不是很多,遇到问题在网上能够找到的资料没有4x全。
- 4x到5x迁移的迁移成本大吗?
- 答:因为5x相比4x主要是在使用上写法变了,大部分API和4x都是一致的,从现在迁移的经验上来看总体迁移成本并不大。
- 4x还在更新吗,可以不迁移到5x吗?
- 答:从React Navigation的官库上看,目前官方还一直在不停的更新4x的版本,因为目前业界大部分公司用的版本还主要集中在4x、3x上,所以除非有特殊要求需要使用5x,否则可以继续使用4x的版本。
迁移指南
包依赖迁移
在5x中对应的包名发生了变化,要完成4x到5x的迁移,首先我们需要将下面的包迁移到5x中去:
4x的中的包 | 对应5x中的包 |
---|---|
react-navigation | @react-navigation/native |
react-navigation-stack | @react-navigation/stack |
react-navigation-tabs | @react-navigation/bottom-tabs, @react-navigation/material-top-tabs |
react-navigation-material-bottom-tabs | @react-navigation/material-bottom-tabs |
react-navigation-drawer | @react-navigation/drawer |
我们只需要对照上表,将我们在package.json中依赖的4x的包删除,然后重新安装对应右侧5x的包即可。
Navigation Container迁移
在React Navigation 5x 由NavigationContainer
中来代替4x的createAppContainer
:
import { NavigationContainer } from '@react-navigation/native';
export default function App() {
return <NavigationContainer>{/*...*/}</NavigationContainer>;
}
另外,5x中NavigationContainer
的onStateChange
API用来代替createAppContainer
的onNavigationStateChange
,下文会重点介绍。
提示:如果你的项目中需要用到多个
NavigationContainer
嵌套的情况,那么需要在被嵌套的NavigationContainer
中设置independent={true}
:
<NavigationContainer
independent={true}
>
...
路由配置迁移
在React Navigation 4.x,我们通常使用createXNavigator
函数来创建对已的导航器配置,在5x中则是通过XX.Navigator + XX.Screen 以组件的方式进行配置的:
4x的配置:
const RootStack = createStackNavigator(
{
Home: {
screen: HomeScreen,
navigationOptions: { title: 'My app' },
},
Profile: {
screen: ProfileScreen,
params: { user: 'me' },
},
},
{
initialRouteName: 'Home',
defaultNavigationOptions: {
gestureEnabled: false,
},
}
);
对应5x的配置:
const Stack = createStackNavigator();
function RootStack() {
return (
<Stack.Navigator
initialRouteName="Home"
screenOptions={{ gestureEnabled: false }}
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'My app' }}
/>
<Stack.Screen
name="Profile"
component={ProfileScreen}
initialParams={{ user: 'me' }}
/>
</Stack.Navigator>
);
}
不难发现在5x中所有的配置是通过props的方式传递个navigator的。
- 另外,通过一个 Screen 元素来表示一个页面;
params
变成了initialParams
navigationOptions
变成了options
defaultNavigationOptions
变成了screenOptions
关于navigation prop的不同
navigation prop
:主要包含navigate
,goBack
等在内的一些工具方法;route prop
:则包含之前navigation.state
在内的一些页面的数据;
这点影响最大的就我们之前从this.props.navigation.state.params
中取数据,现在要改成this.props.route.params
中取数据,可以对比下我们DetailPage.js
迁移所做的修改;
不支持Switch Navigator
5x不在为Switch Navigator提供支持,关于它的替代方案,我会在[NavigationUtil.js修改](https://git.imooc.com/coding-304/GitHub_Advanced/src/7398f2e9d3fc71399d1baa9ada5887bd649f478f/js/navigator/NavigationUtil.js)
的部分进行讲解。
如何将本课程项目Github_RN
从4x迁移都5x?
讲过上述的内容学习之后,接下来我们就将我们课程中Github_RN
从4x迁移到5x,以下是迁移步骤:
在 package.json中移除:
react-navigation
react-navigation-stack
react-navigation-tabs
然后安装:
@react-navigation/bottom-tabs
@react-navigation/material-top-tabs
@react-navigation/native
@react-navigation/stack
react-native-tab-view
将AppNavigators.js
中的代码替换为:
import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
//@ https://github.com/react-navigation/react-navigation/releases/tag/v4.0.0
import WelcomePage from '../page/WelcomePage';
import HomePage from '../page/HomePage';
import WebViewPage from '../page/WebViewPage';
import DetailPage from '../page/DetailPage';
import SortKeyPage from '../page/SortKeyPage';
import SearchPage from '../page/SearchPage';
import CustomKeyPage from '../page/CustomKeyPage';
import AboutPage from '../page/about/AboutPage';
import AboutMePage from '../page/about/AboutMePage';
import CodePushPage from '../page/CodePushPage';
const Stack = createStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="WelcomePage" component={WelcomePage}
options={{headerShown: false}}/>
<Stack.Screen name="HomePage" component={HomePage}
options={{headerShown: false, animationEnabled: false}}/>
<Stack.Screen name="DetailPage" component={DetailPage}
options={{headerShown: false}}/>
<Stack.Screen name="WebViewPage" component={WebViewPage}
options={{headerShown: false}}/>
<Stack.Screen name="AboutPage" component={AboutPage}
options={{headerShown: false}}/>
<Stack.Screen name="AboutMePage" component={AboutMePage}
options={{headerShown: false}}/>
<Stack.Screen name="CustomKeyPage" component={CustomKeyPage}
options={{headerShown: false}}/>
<Stack.Screen name="SortKeyPage" component={SortKeyPage}
options={{headerShown: false}}/>
<Stack.Screen name="SearchPage" component={SearchPage}
options={{headerShown: false}}/>
<Stack.Screen name="CodePushPage" component={CodePushPage}
options={{headerShown: false}}/>
</Stack.Navigator>
</NavigationContainer>
);
}
提示:从上述代码中不难发现
HomePage
的配置比其他页面的配置多了个animationEnabled: false
,这是为了关闭别的页面跳转到首页时的转场动画而加的,如果大家在项目研发中也有关闭页面转场动画的需求可以参照上述方式来实现。
另外,需要注意我们AppNavigators.js中的代码替换完之后,那它的调用的地方也需要进行相应的修改:
App.js
...
render() {
const App = AppNavigator();
/**
* 将store传递给App框架
*/
return <Provider store={store}>
{App}
</Provider>;
}
...
将DynamicTabNavigator.js中的代码替换为:
import React, {Component} from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {BottomTabBar, createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {connect} from 'react-redux';
import PopularPage from '../page/PopularPage';
import TrendingPage from '../page/TrendingPage';
import FavoritePage from '../page/FavoritePage';
import MyPage from '../page/MyPage';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import Ionicons from 'react-native-vector-icons/Ionicons';
import Entypo from 'react-native-vector-icons/Entypo';
import EventTypes from '../util/EventTypes';
import EventBus from 'react-native-event-bus';
const Tab = createBottomTabNavigator();
type Props = {};
const TABS = {//在这里配置页面的路由
PopularPage: {
screen: PopularPage,
navigationOptions: {
tabBarLabel: '最热',
tabBarIcon: ({color, focused}) => (
<MaterialIcons
name={'whatshot'}
size={26}
style={{color: color}}
/>
),
},
},
TrendingPage: {
screen: TrendingPage,
navigationOptions: {
tabBarLabel: '趋势',
tabBarIcon: ({color, focused}) => (
<Ionicons
name={'md-trending-up'}
size={26}
style={{color: color}}
/>
),
},
},
FavoritePage: {
screen: FavoritePage,
navigationOptions: {
tabBarLabel: '收藏',
tabBarIcon: ({color, focused}) => (
<MaterialIcons
name={'favorite'}
size={26}
style={{color: color}}
/>
),
},
},
MyPage: {
screen: MyPage,
navigationOptions: {
tabBarLabel: '我的',
tabBarIcon: ({color, focused}) => (
<Entypo
name={'user'}
size={26}
style={{color: color}}
/>
),
},
},
};
class DynamicTabNavigator extends Component<Props> {
constructor(props) {
super(props);
console.disableYellowBox = true;
}
fireEvent(navigationState) {
const {index, history, routeNames} = navigationState;
if (history.length === 1) {
return;
}
let fromIndex;
let key = history[history.length - 2].key;
for (let i = 0; i < routeNames.length; i++) {
if (key.startsWith(routeNames[i])) {
fromIndex = i;
break;
}
}
EventBus.getInstance().fireEvent(EventTypes.bottom_tab_select, {//发送底部tab切换的事件
from: fromIndex,
to: index,
});
}
_tabNavigator() {
if (this.Tabs) {
return this.Tabs;
}
const {PopularPage, TrendingPage, FavoritePage, MyPage} = TABS;
const tabs = {PopularPage, TrendingPage, FavoritePage, MyPage};//根据需要定制显示的tab
PopularPage.navigationOptions.tabBarLabel = '最热';//动态配置Tab属性
return this.Tabs = <NavigationContainer
independent={true}
>
<Tab.Navigator
tabBar={props => {
this.fireEvent(props.state);
return <TabBarComponent theme={this.props.theme} {...props}/>;
}}
>
{
Object.entries(tabs).map(item => {
return <Tab.Screen
name={item[0]}
component={item[1].screen}
options={item[1].navigationOptions}
/>;
})
}
</Tab.Navigator>
</NavigationContainer>;
}
render() {
const Tab = this._tabNavigator();
return Tab;
}
}
class TabBarComponent extends React.Component {
constructor(props) {
super(props);
this.theme = {
tintColor: props.activeTintColor,
updateTime: new Date().getTime(),
};
}
render() {
return <BottomTabBar
{...this.props}
activeTintColor={this.props.theme.themeColor}
/>;
}
}
const mapStateToProps = state => ({
theme: state.theme.theme,
});
export default connect(mapStateToProps)(DynamicTabNavigator);
上述代码大家直接替换你的对应文件即可,但要注意以下几个经验技巧:
- 关于tabBarIcon的使用;
- tabBar的使用;
- 关于在5x中动态创建底部导航器的技巧;
- 关于在5x中监听tab切换的技巧;
关于tabBarIcon的使用
4x的tabBarIcon中tintColor
在5x中叫color
,使用时需要注意;
tabBar的使用
4x的中的tabBarComponent
在5x中叫tabBar
,使用时需要注意;
关于在5x中动态创建底部导航器的技巧
在4x中我们通过createBottomTabNavigator(tabs,...
的方式动态创建了一个底部导航器,这种方式在5x中就不奏效了,在5x中推荐大家借助ES7的新特性Object.entries
来创建动态导航器:
···
<Tab.Navigator
tabBar={props => {
this.fireEvent(props.state);
return <TabBarComponent theme={this.props.theme} {...props}/>;
}}
>
{
Object.entries(tabs).map(item => {
return <Tab.Screen
name={item[0]}
component={item[1].screen}
options={item[1].navigationOptions}
/>;
})
}
</Tab.Navigator>
···
其中name
表示页面对应的路由名,component
是对应的该页面所展示的对应组件,options
对应4x的navigationOptions
。
关于在5x中监听tab切换的技巧
上文中我们讲到4x的onNavigationStateChange
在5x中被改成了onStateChange
,使用它我们可以监听NavigationContainer
节点下一级页面
的切换,然后可以通过onStateChange
中的回调参数来判断切换到的页面,但5x存在一个bug也就是当你在APP中存在NavigationContainer
嵌套时,里层NavigationContainer
的onStateChange
在回调时会缺失参数,从而无法判断页面切换。
为解决这个问题,有个小技巧就是我们在tabBar
的回调方法中进行页面切换的判断,没有切换tab时tabBar都会被回调,同时会携带index, history, routeNames
等参数,我们可以从这些参数中获取到当前切换到的页面,以及上一个页面对应的索引:
<Tab.Navigator
tabBar={props => {
this.fireEvent(props.state);
return <TabBarComponent theme={this.props.theme} {...props}/>;
}}
>
...
fireEvent(navigationState) {
const {index, history, routeNames} = navigationState;
if (history.length === 1) {
return;
}
let fromIndex;
let key = history[history.length - 2].key;
for (let i = 0; i < routeNames.length; i++) {
if (key.startsWith(routeNames[i])) {
fromIndex = i;
break;
}
}
EventBus.getInstance().fireEvent(EventTypes.bottom_tab_select, {//发送底部tab切换的事件
from: fromIndex,
to: index,
});
}
NavigationUtil.js修改的修改只有一处,主要是为了弥补5x不支持Switch Navigator的问题:
static resetToHomPage(params) {
const {navigation} = params;
navigation.navigate('Main');
}
修改成:
static resetToHomPage(params) {
const {navigation} = params;
// navigation.navigate("HomePage");
navigation.dispatch(
StackActions.replace('HomePage', {}),
);
}
在上述代码中我们通过StackActions.replace
API来实现了Switch Navigator的效果。
我们在PopularPage.js
中用到了createAppContainer
与createMaterialTopTabNavigator
所以也需要进行相应的修改:
主要修改是将:
render() {
...
const TabNavigator = keys.length ? createAppContainer(createMaterialTopTabNavigator(
this._genTabs(), {
tabBarOptions: {
tabStyle: styles.tabStyle,
upperCaseLabel: false,//是否使标签大写,默认为true
scrollEnabled: true,//是否支持 选项卡滚动,默认false
style: {
backgroundColor: theme.themeColor,//TabBar 的背景颜色
// 移除以适配react-navigation4x
// height: 30//fix 开启scrollEnabled后再Android上初次加载时闪烁问题
},
indicatorStyle: styles.indicatorStyle,//标签指示器的样式
labelStyle: styles.labelStyle,//文字的样式
},
lazy: true,
},
)) : null;
return <View style={styles.container}>
{navigationBar}
{TabNavigator && <TabNavigator/>}
</View>;
}
修改为:
render() {
...
const TabNavigator = keys.length ? <NavigationContainer
independent={true}
>
<Tab.Navigator
lazy={true}
tabBarOptions={
{
tabStyle: styles.tabStyle,
// upperCaseLabel: false,//5x 暂不支持标签大写控制
scrollEnabled: true,//是否支持 选项卡滚动,默认false
activeTintColor: 'white',
style: {
backgroundColor: theme.themeColor,//TabBar 的背景颜色
// 移除以适配react-navigation4x
// height: 30//fix 开启scrollEnabled后再Android上初次加载时闪烁问题
},
indicatorStyle: styles.indicatorStyle,//标签指示器的样式
labelStyle: styles.labelStyle,//文字的样式
}
}
>
{
Object.entries(this._genTabs()).map(item => {
return <Tab.Screen
name={item[0]}
component={item[1].screen}
options={item[1].navigationOptions}
/>;
})
}
</Tab.Navigator>
</NavigationContainer> : null;
return <View style={styles.container}>
{navigationBar}
{TabNavigator}
</View>;
}
上述思路和上文中在DynamicTabNavigator.js修改中进行动态底部导航器所做的修改是一样的,同样道理我们在TrendingPage.js,FavoritePage.js中需要做的修改也是一样的思路,具体可以参考我们仓库中的源码。
接下来其它文件的修改思路类似,详情参考4x迁移到5x的代码变更对比。
5x还不支持的功能
- 不支持Switch Navigator导航器
- …
最后
本教程主要针对课程源码迁移过程中的经验和新的的分享,无法做到所有地方都面面俱到,对于考虑不到的地方大家也可以参考下react-navigation官方提供的迁移文档upgrading-from-4.x。
共同学习,写下你的评论
评论加载中...
作者其他优质文章