本文是基于最新的react-navigation
^3.x
来书写的。
以下15条,在我开源的识兔中,都是可以找到实例的,欢迎参考,欢迎star
关于react-navigation
的文章,这已经是第三篇了,这个库从最初的beta
版到最新的2.x
版本,更新频率是很快的,这个库也越来越完善,很多1.x
的技巧已经完全不适用于新的版本,然而群里每天又有很多人再问,为了解(jiao)决(yu)这个问题,所以,动手写了这篇文章。
react-navigation3.x
!因为
react-navigation
版本更新啦,本文又一次更新啦!
react-navigation使用技巧 : 适合初学者,基本讲解了2.x的api,3.x待更新
react-navigation使用技巧(进阶篇) : 适合遇到某些问些不知如何解决的人,廉颇老矣,尚能饭否。
react-navigation使用技巧(再进阶) : 本篇文章,适合对于新版本(2.x,3.x)有疑问的人,对新的Api做了讲解
react-navigation3.x
报错因为在新版本中,新增了一个原生库react-native-gesture-handler
,所以,不管是升级还是新安装,都需要安装这个库,如果已经安装过了,请无视。
安装并link过后,能解决百分之50的问题。下一个解决另一半问题
yarn add react-native-gesture-handler
react-native link react-native-gesture-handler
3.x
后,需要将最外层的包裹形式修改为createAppContainer
在之前的版本中,使用createStackNavigator
后,就会自动实现createAppContainer
,但在新版本中,需要手动使用createAppContainer
来包裹最外层的路由。
以 识兔代码 为例
export const AuthLoadingRouter = createAppContainer(
createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
AppRouter: AppRouter,
AuthRouter: AuthRouter
},
{
initialRouteName: 'AuthLoading'
}
)
);
createAppContainer
提供了两个方法来使用
onNavigationStateChange
: 每次导航器管理的导航状态发生变化时调用的函数。它接收之前的状态,新的状态和发出状态变化的行为。默认情况下,它会将状态更改打印到控制台。
uriPrefix
:深度链接,处理深层链接路径时的方法
作为一个前iOS开发者,从react-navigation
beta版开始,就一直再等待类似iOS
的present
动画效果,虽然,可以使用mode: modal
来实现效果,但这个是全局的,使用这个方法后,所有的页面跳转都是modal
效果,这是不能容忍的。
在1.x
之前,我试过用拆分StackNavigator
的方式实现了效果,但代码不忍直视。
在3.x
中,终于可以手动配置,让某些路由实现想要的动画了。
还是以识兔代码为例,我将跳转登录的代码,改成了Modal
动画。
import { createBottomTabNavigator, createStackNavigator, StackViewTransitionConfigs } from 'react-navigation';
// 数组中的路由,可以自定义动画效果,这里我只改了登录
const IOS_MODAL_ROUTES = ['Login'];
const dynamicModalTransition = (transitionProps, prevTransitionProps) => {
const isModal = IOS_MODAL_ROUTES.some(
screenName =>
screenName === transitionProps.scene.route.routeName ||
(prevTransitionProps && screenName === prevTransitionProps.scene.route.routeName)
)
return StackViewTransitionConfigs.defaultTransitionConfig(
transitionProps,
prevTransitionProps,
isModal
);
};
const HomeStack = createStackNavigator({
MyTab: {
screen: MyTab
},
BuDeJie: {
screen: BuDeJie
},
Login: {
screen: Login
}
},{
initialRouteName: 'MyTab',
transitionConfig: dynamicModalTransition
});
这个也是3.x
版本中,新增的东西,react-navigation
导出了ScrollView
,FlatList
和SectionList
。
导出的三个组件中,方法和原来的一样,只不过在内部实现了,滚动到顶部的方法。
看issues
上有很多bug哦,慎用。
之前初始化配置路由属性都是在navigationOptions
中,这样虽然更便捷,但如果想在页面中修改却不行,不会覆盖初始值,在新版本中提供了defaultNavigationOptions
,用法和之前一样,但终于可以在页面中覆盖初值了
因为在react-navigation2.x
版本中,作者将该库的路由包裹方式改了,之前TabNavigator
中是包含了StackNavigator
大部分属性的,所以,可以很简单的设置header
,headerStyle
,headerTitle
等属性的。
新版本中,createBottomTabNavigator
没有了这些属性,如果想要修改这些属性,有两种方式:
- 用
createStackNavigator
初始化页面,然后再用createBottomTabNavigator
包裹再外层,最外层再用createStackNavigator
包裹一遍,用来跳转其他子页面。
const ShiTuStack = createStackNavigator({
ShiTu: ShiTu,
});
const BuDeJieStack = createStackNavigator({
BuDeJie: BuDeJie,
BuDeJieDetail: BuDeJieDetail,
});
const MyTab = createBottomTabNavigator({
Tabs: ShiTuStack,
Details: BuDeJieStack,
});
const AppRouter = createStackNavigator({
Auth: AuthScreen,
MyTab: MyTab,
});
- 使用
setParams
属性,在根路由页面的componentDidMount
中调用
this.props.navigation.setParams({title: '识兔'});
接下来在路由页面中
export const AppRouter = createStackNavigator({
MyTab: {
screen: MyTab,
},
BuDeJie: {
screen: BuDeJie,
},
}, {
navigationOptions: ({navigation}) => NavigatorOptions(navigation)
}
const NavigatorOptions = (navigation) => {
const routes = navigation.state.routes;
// 通过params得到传进来的title,并赋值给headerTitle。
const params = routes ? routes[navigation.state.index].params : null;
const headerTitle = params ? params.title : '';
const headerTitleStyle = {
fontSize: System.iOS ? 23 : 20,
color: 'white',
flex: 1,
textAlign: 'center',
paddingTop: System.Android ? 17 : null,
};
const headerBackTitle = null;
const headerTintColor = 'white';
const headerStyle = {
backgroundColor: Theme.navColor,
shadowColor: 'transparent',
shadowOpacity: 0,
borderBottomWidth: 0,
borderBottomColor: 'transparent',
elevation: 0,
};
const header = null;
return { headerTitle, headerStyle, headerTitleStyle, headerBackTitle, headerTintColor, header };
};
以上的两种方式,并不是很好的方式,react-navigation
导航条的自定义性虽然越来越强了,但某些情况下还是没有完全自定义的导航更好控制,比如说我想监听到react-navigation
自带导航的返回按钮,只能去页面中复写headerLeft
属性,这样就不如,完全控制了。
我在识兔中,提供了一套基于
teaset
的导航 + 适配页面,欢迎使用哦!
这个是老生常谈的问题了。我之前更新的文章中,有1.x
版本和2.10.x
版本之前的实现方式,但2.13.0
又改了,这里把三种方式都整理出来。
三种的用法是一样的,只不过,引入文件的路径有修改。先把用法发出来。
{
// 快速定制导航条,新版识兔中所有的导航都是重写的,所以这里会将全部的导航置空
navigationOptions: () => ({
header: null,
gesturesEnabled: true,
}),
transitionConfig: () => ({
screenInterpolator: StackViewStyleInterpolator.forHorizontal,
})
}
官方一共提供了四种动画方式
从右向左: forHorizontal iOS默认效果
从下向上: forVertical
安卓那种的从下向上: forFadeFromBottomAndroid
无动画: forInitial
如果想自定义的话,可以使用官方推荐的三方库FluidTransitions。
3.11.0
import StackViewStyleInterpolator from 'react-navigation-stack/src/views/StackView/StackViewStyleInterpolator';
2.13.0
import StackViewStyleInterpolator from
'react-navigation-stack/dist/views/StackView/StackViewStyleInterpolator';
2.x
import StackViewStyleInterpolator from
'react-navigation/src/views/StackView/StackViewStyleInterpolator';
1.x
import CardStackStyleInterpolator from
'react-navigation/src/views/CardStack/CardStackStyleInterpolator';
react-navigation
最初的版本是没有这个事件的,那个时候,我手写了这个事件并暴露出去,后来官方添加了这个事件,只不过1.x
和2.x
的返回属性不一样,但方法名是一样的。
2.x
tabBarOnPress: async (obj: any) => {
console.log(obj);
try {
const userData = await AsyncStorage.getItem('USER_INFO');
if (userData) {
obj.defaultHandler();
}
else {
obj.navigation.navigate('Login');
}
} catch (e) {
Toast.show(e.message, 'center', 1000);
}
}
1.x
tabBarOnPress:(obj)=>{
console.log(obj);
obj.jumpToIndex(obj.scene.index)
}
经晴明大神
指点,将该方法更新
重复跳转是可以用过push
跳转的,但容易发生的问题就是可能会导致重复跳转该页面,而用navigate
使通过key
控制的,所以,基本可以保证不会重复跳转,而直接使用this.props.navigate.navigate('Detail')
是不可行的,需要手动设置key
,作为唯一标识。
this.props.navigation.navigate({
key: user.id,
routeName: 'Detail'
})
想去同一个页面可以用navigate
,但新版本中,这么做却不行了,因为navigate
是根据key
查找页面的,如果页面入栈就不跳转。 这里要使用不算新的apipush
咯,才能实现重复跳转。
在开发RN的过程中,经常会遇到,我在开发一个页面,reload
之后,又要重新进去,在2.x
版本中,新增了一个实验性的apipersistenceKey
,它会自动保存当前页面的路由,并在reload
之后默认打开该页面。
以识兔中路由层index.js
为例
const navigationPersistenceKey = __DEV__ ? 'NavigationStateDEV' : null;
}
/>
persistenceKey
就是保持当前页面的key,通过存入AsyncStorage
,在下次进入页面后,通过读取这个key来打开相应的页面。
renderLoadingExperimental
因为AsyncStorage
是异步加载的,所以在取值过程中可能出现闪白的情况,可以使用这个属性,呈现加载视图
注:以上方式是实验性方法,可能在未来的版本中有变更
如果开发过原生都会知道,原生中每个页面都是有独立的生命周期的,以iOS
为例。
viewWillAppear
: 控制器的view将要显示
viewDidLoad
:view加载完毕
viewWillDisappear
:控制器的view即将消失的时候
viewDidAppear
:控制器的view完全显示
从开发RN第一天开始,就很期待有这些生命周期,但React
能用的页面生命周期很少,常用的有
componentWillMount(快被废弃了)
:页面将要显示
componentDidMount
:页面已经显示
componentWillUnmount
:页面将要消失
在react-navigation
中终于实现了这个心愿,为页面添加了可用的生命周期。
onWillBlur
:页面将要失去焦点
onDidBlur
:页面已经失去焦点
onWillFocus
:页面将要获得焦点
onDidFocus
:页面已经获得焦点
react-navigation
提供了两种方式获取这个生命周期
手动监听
componentDidMount() {
// 通过addListener开启监听,可以使用上面的四个属性
this._didBlurSubscription = this.props.navigation.addListener(
'didBlur',
payload => {
console.debug('didBlur', payload);
}
);
}
componentWillUnmount() {
// 在页面消失的时候,取消监听
this._didBlurSubscription && this._didBlurSubscription.remove();
}
通过组件方法监听
下面这种方式,会自动处理取消监听
介绍完生命周期之后,之前存在的各种问题都迎刃而解了,只要活用这几个生命周期,能完成很多麻烦的问题,比如说安卓的返回键,之前的处理方式是在componentDidMount
订阅事件,在componentWillUnmount
取消事件,但是componentWillUnmount
只有在页面销毁的时候才会触发,这样就导致,很多时候要把返回事件写在很多个页面,分别监听和销毁,有了声明周期这个就简单了。
constructor(props) {
super(props);
this._didFocusSubscription = props.navigation.addListener('didFocus', payload =>
BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
);
}
componentDidMount() {
this._willBlurSubscription = this.props.navigation.addListener('willBlur', payload =>
BackHandler.removeEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
);
}
onBackButtonPressAndroid = () => {
if (this.isSelectionModeEnabled()) {
this.disableSelectionMode();
return true;
} else {
return false;
}
};
componentWillUnmount() {
this._didFocusSubscription && this._didFocusSubscription.remove();
this._willBlurSubscription && this._willBlurSubscription.remove();
}
render() {
// ...
}
react-navigation
中也提供了SafeAreaView
这个组件,它会自动处理顶部导航栏和底部标签栏的适配,并且也会自动适配安卓的异形屏。用法和react-native
提供的也类似。
render() {
return (
This is top text.
This is bottom text.
);
}
navigation
对象在很多情况下,我们的组件或者页面需要得到this.props.navigation
对象,但是会报错,说没有找到navigation
对象,为了解决这个问题,在react-navigation
中提供了一个高阶组件withNavigation
来应对。
注:页面中没有navigation
对象,一般都是没有在初始化路由的时候注册该页面,所以请先检查自己的写法,然后再使用该组件
注:该组件最好的使用场景,应该是组件中,比如说返回按钮,或者跳转按钮等等
在这里提供识兔中返回按钮的代码
import { withNavigation } from 'react-navigation';
class NavigatorBar extends React.PureComponent {
backButtonPress = () => {
const {backButtonPress} = this.props;
if (backButtonPress) {
backButtonPress();
} else {
this.props.navigation.goBack();
}
}
renderLeftView = () => {
const {isTopNavigator, leftView, } = this.props;
let left;
if (isTopNavigator || leftView) {
left = leftView;
} else {
left = ;
}
return left;
}
render() {
return (
);
}
}
export default withNavigation(NavigatorBar);
正常情况下,都会用createStackNavigator
包裹createBottomTabNavigator
,但就是存在不正常的情况呢? 那应该怎么处理tab的隐藏显示呢?其实很简单
const AppRouter = createStackNavigator({
MyTab: MyTab,
BuDeJie: BuDeJie,
});
BuDeJie.navigationOptions = ({ navigation }) => {
let tabBarVisible = true;
if (navigation.state.index > 0) {
tabBarVisible = false;
}
return {
tabBarVisible,
};
};
以上是我整理的15条关于新版react-navigation
的进阶教程,如果还需要什么新的教程,欢迎加入QQ群397885169一起学习,一起成长。
react-navigation3.x
版本,强烈推荐更新,虽然还是有一些痛点,但可以看到这个库还是在不断完善的,相信它和RN会越来越好。
以上10条,在我开源的识兔中,都是可以找到实例的,欢迎参考,欢迎star
作者:挂着铃铛的兔
链接:https://www.jianshu.com/p/dc9df5826651
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。