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

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



作者:挂着铃铛的兔
链接:https://www.jianshu.com/p/dc9df5826651
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值