提示:本教程不定期更新,请关注课程章节列表及时获取更新
在这个教程中将向大家分享React Navigation 4x到5x的迁移指南,本教程主要以课程源码为例讲解4x到5x迁移过程中所需要做的事情以及一些经验和心得。
如果你的项目还没有使用4x版本,那么可以直接参考下面教程按照和使用4x或5x版本的导航器:
Github_RN
从4x迁移都5x?已经迁移到5x后的课程源码:
在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的包即可。
在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的。
params
变成了 initialParams
navigationOptions
变成了options
defaultNavigationOptions
变成了screenOptions
navigation prop
:主要包含navigate
, goBack
等在内的一些工具方法;route prop
:则包含之前navigation.state
在内的一些页面的数据;这点影响最大的就我们之前从this.props.navigation.state.params
中取数据,现在要改成this.props.route.params
中取数据,可以对比下我们DetailPage.js
迁移所做的修改;
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);
上述代码大家直接替换你的对应文件即可,但要注意以下几个经验技巧:
4x的tabBarIcon中tintColor
在5x中叫color
,使用时需要注意;
4x的中的tabBarComponent
在5x中叫tabBar
,使用时需要注意;
在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
。
上文中我们讲到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的代码变更对比。
本教程主要针对课程源码迁移过程中的经验和新的的分享,无法做到所有地方都面面俱到,对于考虑不到的地方大家也可以参考下react-navigation官方提供的迁移文档upgrading-from-4.x。