React导航

前面三篇文章我们分别介绍了React基础Flexbox模型手势响应,今天我们介绍任何软件,系统都很重要的导航部分。

对于Android开发人员来讲(IOS猿请忽略),Android页面之间的跳转是由工作栈维护的,进入一个页面,一般的是入栈,而退出一个页面,就意味着出栈。我们在开发app的时候,之所以没有感觉到出栈入栈,是因为Android将这些都封装好了。React Native则比较粗暴,你从它页面的跳转方法就可以明显的看出,它是由栈实现的,归结到组件的话就是Navigator组件(IOS有NavigatorIOS组件,相较于Navigator,效率更高)。下面来看一个简单的例子:

import FirstPageComponent from './FirstPageComponent';  

class demoReact extends Component {
    render() {
      let defaultName = 'FirstPageComponent';
      let defaultComponent = FirstPageComponent;
      let defaultTitle = "First Page";

      return (
        <Navigator
            initialRoute=双大括号
              name: defaultName,
              component: defaultComponent,
              title: defaultTitle,
            }}
            configureScene={(route) => {
                       return Navigator.SceneConfigs.VerticalDownSwipeJump;
                     }}
            renderScene={(route, navigator) => {
                       let Component = route.component;
                       return <Component {...route.params} navigator={navigator} />
                     }}
        />
      );
    }

};  其中:
  • initialRoute:定义了启动加载时的路由,上面的示例定义了组件名称,组件和页面的标题。
  • configureScene:定义了页面切换时的过渡动画,可选的值有:
    • Navigator.SceneConfigs.PushFromRight (默认)
    • Navigator.SceneConfigs.FloatFromRight
    • Navigator.SceneConfigs.FloatFromLeft
    • Navigator.SceneConfigs.FloatFromBottom
    • Navigator.SceneConfigs.FloatFromBottomAndroid
    • Navigator.SceneConfigs.FadeAndroid
    • Navigator.SceneConfigs.HorizontalSwipeJump
    • Navigator.SceneConfigs.HorizontalSwipeJumpFromRight
    • Navigator.SceneConfigs.VerticalUpSwipeJump
    • Navigator.SceneConfigs.VerticalDownSwipeJump
  • renderScene:此方法时必要参数。用来渲染指定路由的场景。调用的参数是路由和导航器。 FirstPageComponent.js的代码如下:

    import React from ‘react’;

    import {

    View,
    Navigator,
    TouchableOpacity,
    Text,
    StyleSheet
    

    } from ‘react-native’;

    import SecondPageComponent from ‘./SecondPageComponent’;

    export default class FirstPageComponent extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
          id:1,
          user:null,
        };
    }
    
    componentDidMount() {
    }
    
    _pressButton() {
        const { navigator } = this.props;
        if(navigator) {
            navigator.push({
                name: 'SecondPageComponent',
                component: SecondPageComponent,
                title:'Second Page',
                params:{
                  id:this.state.id,
                  getUser: (user) => {
                      this.setState({
                          user: user
                      })
                  }
                }
    
            })
        }
    }
    render() {
          if(this.state.user) {
                  return(
                      <View style={styles.container}>
                          <Text>用户信息: { JSON.stringify(this.state.user) }</Text>
                      </View>
                  );
              }else {
                  return(
                      <View style={styles.container}>
                          <TouchableOpacity onPress={this._pressButton.bind(this)} style={styles.userLayout}>
                              <Text >查询ID为{ this.state.id }的用户信息</Text>
                          </TouchableOpacity>
                      </View>
                  );
              }
    }
    

    }

    var styles = StyleSheet.create({

    container: {
      justifyContent: 'center',
      alignItems: 'center',
      backgroundColor: '#F5FCFF',
      marginTop:100,
    },
    userLayout:{
      borderWidth:1,
      borderColor:'black',
      padding:10,
    }     });
    

SecondPageComponent.js的代码
import React from ‘react’;

import {
    View,
    Navigator,
    Text,
    TouchableOpacity,
    StyleSheet
} from 'react-native';

import FirstPageComponent from './FirstPageComponent';

const USER_MODELS = {
    1: { name: 'fyales一号', age: 23 },
    2: { name: 'fyales二号', age: 25 }
};

export default class SecondPageComponent extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
          id:null,
          title:"Second Page",
        };
    }

    componentDidMount(){
      this.setState({
        id:this.props.id,
        title:this.props.title,
      });
    }

    _pressButton() {
        const { navigator } = this.props;

        if(this.props.getUser) {
               let user = USER_MODELS[this.props.id];
               this.props.getUser(user);
           }

        if(navigator) {
            //出栈
            navigator.pop();
        }
    }

    render() {
    return (
            <View style = {styles.container}>
            <Text>获得的参数: id={ this.state.id }</Text>
            <TouchableOpacity onPress={this._pressButton.bind(this)} style={styles.backLayout}>
                <Text>返回</Text>
            </TouchableOpacity>
            </View>
    );
    }
}

var styles = StyleSheet.create({
  container: {
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
    marginTop:100,
  },
  backLayout:{
    borderWidth:1,
    borderColor:'black',
    padding:10,
  }
});

我们可以先看看FirstPageComponent.js的跳转逻辑,navigator.push()方法就是将SecondPage入栈,同样的,我们通过定义组件名称,组件来找到相应的文件,并将下一个页面需要展示的title传到下一个页面。而且,我们通过params参数传了一些数据和一个回调函数给下一个页面。这里需要注意的是,states存储的一般的是当前页面的值,而props存储的一般是其他页面传给本页面的值。

在我们从FirstPage跳到SecondPage之后,再点击返回按钮的时候,_pressButton方法首先执行了第一个页面的回调方法,从而刷新了第一个页面的状态,同时第二个页面出栈,返回到了第一个页面。

除了最基本的push方法和pop方法,navigator还有许多其他方法进行导航:

  • getCurrentRoutes():获取当前栈里的路由,也就是push进来,没有pop掉的那些。
  • jumpBack():跳回之前的路由,当然前提是保留现在的,还可以再跳回来,会给你保留原样。
  • jumpForward():上一个方法不是调到之前的路由了么,用这个跳回来就好了。
  • jumpTo(route): 跳转到已有的场景并且不卸载。
  • push(route) :跳转到新的场景,并且将场景入栈,你可以稍后跳转过去
  • pop():跳转回去并且卸载掉当前场景
  • replace(route):用一个新的路由替换掉当前场景
  • replaceAtIndex(route, index):替换掉指定序列的路由场景
  • replacePrevious(route): 替换掉之前的场景
  • resetTo(route):跳转到新的场景,并且重置整个路由栈
  • immediatelyResetRouteStack(routeStack):用新的路由数组来重置路由栈
  • popToRoute(route):pop到路由指定的场景,在整个路由栈中,处于指定场景之后的场景将会被卸载。
  • popToTop():pop到栈中的第一个场景,卸载掉所有的其他场景。

同样的,为了实现类似Android导航栏的功能,React Native提供了NavigationBar(类似于Android的Toolbar,不过个人觉得不太好用,建议用第三方库),加了NavigationBar的index.android.js代码如下:
import React, {
Component,
} from ‘react’;

import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    Navigator,
    TouchableOpacity
} from 'react-native';

import FirstPageComponent from './FirstPageComponent';

    class demoReact extends Component {

        render() {
          let defaultName = 'FirstPageComponent';
          let defaultComponent = FirstPageComponent;
          let defaultTitle = "First Page";

          return (
            <Navigator
                initialRoute=双大括号
                  name: defaultName,
                  component: defaultComponent,
                  title: defaultTitle,
                }}
                configureScene={(route) => {
                           return Navigator.SceneConfigs.FadeAndroid;
                         }}
                renderScene={(route, navigator) => {
                           let Component = route.component;
                           return <Component {...route.params} navigator={navigator} />
                         }}
                navigationBar={
                           <Navigator.NavigationBar
                             routeMapper={NavigationBarRouteMapper}
                             style={styles.navBar}
                           />
                         }
            />
          );
        }

    };
 var NavigationBarRouteMapper = {

  LeftButton: function(route, navigator, index, navState) {
    if (index === 0) {
      return null;
    }

    return (
      <View style={styles.navBarLeftButton}>
      <TouchableOpacity
        onPress={() => navigator.pop()}
        >
        <Text style={styles.navBarText}>
          Return
        </Text>
      </TouchableOpacity>
      </View>
    );
  },

  RightButton: function(route, navigator, index, navState) {
    return (
      <View style={styles.navBarRightButton}>
      <TouchableOpacity
        >
        <Text style={styles.navBarText}>
          Next
        </Text>
      </TouchableOpacity>
      </View>
    );
  },

  Title: function(route, navigator, index, navState) {
    return (
      <View style={styles.navbarTitle}>
      <Text style={styles.navBarTitleText}>
        {route.title}
      </Text>
      </View>
    );
  },

}; 
var styles = StyleSheet.create({
  navBar: {
    flexDirection: 'row',  //只支持column和row两种属性
    flexWrap:'nowrap',  //只支持wrap和nowrap两种属性
    justifyContent:'center',  //主轴
    alignItems:'stretch',  //交叉轴
    backgroundColor: 'orange',
  },
  navBarText: {
    fontSize: 16,
    color:'white',
    textAlign:'center',
  },
  navbarTitle:{
    flex:2,
    alignSelf:'center',
    paddingTop:15,
  },
  navBarTitleText: {
    color: 'white',
    fontSize:18,
    textAlign:'left',
  },
  navBarLeftButton: {
    flex:1,
    paddingLeft: 10,
    paddingTop:18,
    alignSelf:'flex-start',
  },
  navBarRightButton: {
    flex:1,
    paddingRight: 10,
    paddingTop:18,
    alignSelf:'flex-end',
  },

});

AppRegistry.registerComponent('demoReact', () => demoReact);

代码中的NavigationBarRouteMapper是导航栏路由映射器, 设置左边按钮,右边按钮和标题。完整的效果图如下:

第一次进入页面
进入第二页
点击按钮返回

第三方实现

React Native Simple Router是一款第三方导航组件。你可以通过它进行合理的视图组织。

参考

新手理解navigator的教程
React Native 的 Navigator 组件使用方式