React Native学习RNReact Native

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

2018-09-17  本文已影响365人  挂着铃铛的兔

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

本文是基于最新的react-navigation^2.14.2来书写的。

以下10条,在我开源的识兔中,都是可以找到实例的,欢迎参考,欢迎star

前言

关于react-navigation的文章,这已经是第三篇了,这个库从最初的beta版到最新的2.x版本,更新频率是很快的,这个库也越来越完善,很多1.x的技巧已经完全不适用于新的版本,然而群里每天又有很多人再问,为了解(jiao)决(yu)这个问题,所以,动手写了这篇文章。

问题&技巧

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

因为在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的导航 + 适配页面,欢迎使用哦!

2. 安卓实现类似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

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';

3. 让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)
}

4. 重复跳转同一个页面

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

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

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

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

5. 保持页面状态

在开发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是异步加载的,所以在取值过程中可能出现闪白的情况,可以使用这个属性,呈现加载视图

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

6. 页面的生命周期

如果开发过原生都会知道,原生中每个页面都是有独立的生命周期的,以iOS为例。

从开发RN第一天开始,就很期待有这些生命周期,但React能用的页面生命周期很少,常用的有

react-navigation中终于实现了这个心愿,为页面添加了可用的生命周期。

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}   
/>

7. 处理安卓返回键

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

8. 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>
    );
  }

9. 在任何页面或者组件中都得到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);

10. 隐藏标签栏

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

总结

以上是我整理的10条关于新版react-navigation的进阶教程,如果还需要什么新的教程,欢迎加入QQ群397885169一起学习,一起成长。

以上10条,在我开源的识兔中,都是可以找到实例的,欢迎参考,欢迎star

上一篇 下一篇

猜你喜欢

热点阅读