react-navigation使用技巧(再进阶)

如果在学习react-native的过程中遇到什么问题,欢迎加入QQ群397885169一起学习,一起成长。

本文是基于最新的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做了讲解

问题&技巧

1. 安装react-navigation3.x报错

因为在新版本中,新增了一个原生库react-native-gesture-handler,所以,不管是升级还是新安装,都需要安装这个库,如果已经安装过了,请无视。

安装并link过后,能解决百分之50的问题。下一个解决另一半问题

yarn add react-native-gesture-handler
react-native link react-native-gesture-handler

2. 安装3.x后,需要将最外层的包裹形式修改为createAppContainer

在之前的版本中,使用createStackNavigator后,就会自动实现createAppContainer,但在新版本中,需要手动使用createAppContainer来包裹最外层的路由。

识兔代码 为例

export const AuthLoadingRouter = createAppContainer(
  createSwitchNavigator(
    {
      AuthLoading: AuthLoadingScreen,
      AppRouter: AppRouter,
      AuthRouter: AuthRouter
    },
    {
      initialRouteName: 'AuthLoading'
    }
  )
);

createAppContainer 提供了两个方法来使用

onNavigationStateChange: 每次导航器管理的导航状态发生变化时调用的函数。它接收之前的状态,新的状态和发出状态变化的行为。默认情况下,它会将状态更改打印到控制台。

uriPrefix:深度链接,处理深层链接路径时的方法

3. 模态动画

作为一个前iOS开发者,从react-navigationbeta版开始,就一直再等待类似iOSpresent动画效果,虽然,可以使用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
});


4. 点击Tab,滚动到页面顶部

这个也是3.x版本中,新增的东西,react-navigation导出了ScrollViewFlatListSectionList

导出的三个组件中,方法和原来的一样,只不过在内部实现了,滚动到顶部的方法。

issues上有很多bug哦,慎用。

5. 新增defaultNavigationOptions

之前初始化配置路由属性都是在navigationOptions中,这样虽然更便捷,但如果想在页面中修改却不行,不会覆盖初始值,在新版本中提供了defaultNavigationOptions,用法和之前一样,但终于可以在页面中覆盖初值了

6. 为什么无法修改跟路由的导航头,我想修改它的颜色,隐藏等等

因为在react-navigation2.x版本中,作者将该库的路由包裹方式改了,之前TabNavigator中是包含了StackNavigator大部分属性的,所以,可以很简单的设置headerheaderStyleheaderTitle等属性的。
新版本中,createBottomTabNavigator没有了这些属性,如果想要修改这些属性,有两种方式:

  1. 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,
});
  1. 使用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的导航 + 适配页面,欢迎使用哦!

7. 安卓实现类似iOS的push动画

这个是老生常谈的问题了。我之前更新的文章中,有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';

8. 让TabBar拥有点击事件

react-navigation最初的版本是没有这个事件的,那个时候,我手写了这个事件并暴露出去,后来官方添加了这个事件,只不过1.x2.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)
}

9. 重复跳转同一个页面

晴明大神指点,将该方法更新

重复跳转是可以用过push跳转的,但容易发生的问题就是可能会导致重复跳转该页面,而用navigate使通过key控制的,所以,基本可以保证不会重复跳转,而直接使用this.props.navigate.navigate('Detail')是不可行的,需要手动设置key,作为唯一标识。

this.props.navigation.navigate({
    key: user.id,
    routeName: 'Detail'
})

想去同一个页面可以用navigate,但新版本中,这么做却不行了,因为navigate是根据key查找页面的,如果页面入栈就不跳转。 这里要使用不算新的apipush咯,才能实现重复跳转。

10. 保持页面状态

在开发RN的过程中,经常会遇到,我在开发一个页面,reload之后,又要重新进去,在2.x版本中,新增了一个实验性的apipersistenceKey,它会自动保存当前页面的路由,并在reload之后默认打开该页面。

以识兔中路由层index.js为例

const navigationPersistenceKey = __DEV__ ? 'NavigationStateDEV' : null;

<AuthLoadingRouter persistenceKey={navigationPersistenceKey}
                   renderLoadingExperimental={() => <ActivityIndicator size='large' color='black' />}
                    />

persistenceKey就是保持当前页面的key,通过存入AsyncStorage,在下次进入页面后,通过读取这个key来打开相应的页面。

renderLoadingExperimental因为AsyncStorage是异步加载的,所以在取值过程中可能出现闪白的情况,可以使用这个属性,呈现加载视图

注:以上方式是实验性方法,可能在未来的版本中有变更

11. 页面的生命周期

如果开发过原生都会知道,原生中每个页面都是有独立的生命周期的,以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();
}

通过组件方法监听

下面这种方式,会自动处理取消监听

<NavigationEvents 
    onWillFocus={onWillFocus}
    onDidFocus={onDidFocus}
    onWillBlur={onWillBlur}
    onDidBlur={onDidBlur}   
/>

12. 处理安卓返回键

介绍完生命周期之后,之前存在的各种问题都迎刃而解了,只要活用这几个生命周期,能完成很多麻烦的问题,比如说安卓的返回键,之前的处理方式是在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() {
    // ...
  }

13. iPhoneX适配和安卓异形屏适配

react-navigation中也提供了SafeAreaView这个组件,它会自动处理顶部导航栏和底部标签栏的适配,并且也会自动适配安卓的异形屏。用法和react-native提供的也类似。

render() {
    return (
      <SafeAreaView style={styles.container}>
        <Text style={styles.paragraph}>
          This is top text.
        </Text>
        <Text style={styles.paragraph}>
          This is bottom text.
        </Text>
      </SafeAreaView>
    );
  }

14. 在任何页面或者组件中都得到navigation对象

在很多情况下,我们的组件或者页面需要得到this.props.navigation对象,但是会报错,说没有找到navigation对象,为了解决这个问题,在react-navigation中提供了一个高阶组件withNavigation来应对。

注:页面中没有navigation对象,一般都是没有在初始化路由的时候注册该页面,所以请先检查自己的写法,然后再使用该组件
注:该组件最好的使用场景,应该是组件中,比如说返回按钮,或者跳转按钮等等

在这里提供识兔中返回按钮的代码

import { withNavigation } from 'react-navigation';

class NavigatorBar extends React.PureComponent<Props> {
    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 = <NavigationBar.BackButton title='返回' onPress={this.backButtonPress}/>;
        }
        return left;
    }
    
    render() {
        return (
            <NavigationBar leftView={this.renderLeftView()}                         
                           titleStyle={{fontSize: System.iOS ? 23 : 20, color: 'white', fontWeight: 'bold'}}
                           {...this.props}
            />
        );
    }
}
export default withNavigation(NavigatorBar);

15. 隐藏标签栏

正常情况下,都会用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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容