微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

React Native填坑之旅--Navigation篇

React Native的导航有两种,一种是iOS和Android通用的叫做Navigator,一种是支持iOS的叫做NavigatorIOS。我们这里只讨论通用的Navigator。会了Navigator,NavigatorIOS也就不是什么难事了。

本文所使用的是React Native 0.34。FB团队更新的太快了,我会在后续出现大的改动的时候更新本文以及代码

Navigator在不同的Scene之间跳转
* initialRoute对象
这是Navigator所必须的,用于指定第一个Scene。

  • renderScene方法,这个方法必须。用flow的语法来描述的话是这样的renderScene(router: any,navigator: Navigator)renderScene方法用来根据一个给定的route来绘制Scene。如:
(route,navigator) => {
      <MySceneComponent title={route.title} navigator={navigator} /> }

API就了解这么多,下面看一个简单的例子。数据都是写死的。

这个例子的主要功能就是从一个Scene(组件)HomeController,跳转到另外的一个组件PetListController。就是从一组用户里点选一个之后显示这个用户拥有的宠物列表。

代码里的User数据以及用户Pets数据都是写死的。如果要学习网络请求方面的内容可以参考HomeController里的fetchAction方法,以及填坑系列的前篇Http篇。

准备

HomeController,在这个组件里显示用户列表。

import React,{ Component } from 'react';
import {...略...} from 'react-native';

export default class HomeController extends Component {
    state: State;

    constructor(props) {
        super(props);

        const ds = new ListView.DataSource({rowHasChanged: (r1,r2) => r1 !== r2});
        this.state = {
            message: '',dataSource: ds.cloneWithRows(['Micheal','Jack','Paul'])
        };
    }

// ...略...

    render() {
        return (
            <View style={{marginTop: 64}}> <ListView  dataSource={this.state.dataSource} renderRow={this._renderRow.bind(this)} /> </View> ); } };

文中储备要代码都已经略去。

你可以看到,数据源就是一个数组['Micheal','Jack','Paul'],里面有三个人。数据最后显示ListView里。

行渲染的时候,在行的里面添加可以相应点击的TouchableHighlight,在用户点击之后跳转PetListController中。

_renderRow(data: string,sectionID: number,rowID: number,highlightRow: (sectionID: number,rowID: number) => void) {
        return (
            <TouchableHighlight onPress={() => { this._onPressRow(rowID); highlightRow(sectionID,rowID); }}> <View style={styles.row}> <Text style={styles.text}>{data}</Text> </View> </TouchableHighlight> ); }

另外的一个PetListController里只是显示某个用户的宠物列表。

export default class PetListController extends Component {
    state: State;

    constructor(props) {
        super(props);

        const ds = new ListView.DataSource({ rowHasChanged: (r1,r2) => r1 !== r2 });

        this.state = {
            dataSource: ds.cloneWithRows(['Dog 1','Dog 2','Dog 3'])
        };
    }

// ...略...

    render() {
        return (
            <View style={{ marginTop: 64 }}> <ListView  dataSource={this.state.dataSource} renderRow={this._renderRow.bind(this)} renderSeperator={this._renderSeparator.bind(this)} /> </View> ); } };

在这个组件里显示的就是宠物数据。展示方式也是用的ListView

开始导航

在本例中,导航开始的地方不在某个具体的Controller里(组件),而是在index.ios.js,android的在index.android.js里。这么做并不好,以后重构代码的时候会提升到同一个文件中。

我们从Navigator绘制的地方开始导航的讲解:

render() {
    return (
        <View style={styles.container}> <Navigator  initialRoute={this.initialRoute} renderScene={this._renderScene} navigationBar={ <Navigator.NavigationBar routeMapper={NavigationBarRouteMapper} /> } /> </View> ); }

回顾一下最开始的API,renderScene方法是用来绘制每一个Scene(场景)。Sene的实质就是一个个的组件,这个组件会占满一个屏幕。

组件的绘制需要有一些基本的信息,这个信息就是在initialRoute里指定的。

this.initialRoute = {
        title: 'Users',component: HomeController,index: 0,passprops: {
            // 在这里传递其他的参数
        }
    }

这个initialScene一个对象,内容有你自己定。

下面看看Scene的绘制方法renderScene

_renderScene(route: Route,navigator: Navigator) {
    if (route.component) {
        return React.createElement(route.component,{...this.props,...route.passprops,navigator,route});
    }
}

这个方法每次都会返回一个ReactElement实例,和JSX语法返回的是一样的,虽然JSX看起来是这样的<HomeController />

这样写可能对于初学者来说有一点绕,那么更加直观一点的写法是什么样呢?来看看:

_renderScene(route: Route,navigator: Navigator) {
    switch(route.index) {
        case 0: 
            return <HomeController />;
        case 1:
            return <PetListController />;
        default:
            return <HomeController />;
    }
}

这个写法作用就是根据用户当前要访问的Route的index值来绘制相应的组件来作为当前的Scene。

但是,如此写法也略显复杂。你在写这个render方法的时候需要知道全部的导航路劲,即从一开始是哪个Scene,第二部导航到哪个Scene,第三部。。。以此类推。在Navigator路径上有几个Scene就需要写几个。所以使用第一种写法,用createElement方法来,根据指定的组件实例来返回作为Scene使用的组件。

上面的例子运行出来的时候有一个极大的问题,你不注意的话在PetListController没法返回到HomeController。

界面上并没有返回按钮,但是RN居然把iOS的在最左侧的手势拖动返回上一级的功能实现了。这个功能在Android的实现上也有。总之隐藏不见的这个功能用户体验上会有很大的问题。

所以必须用到Navigator的NavigationBar

<Navigator
  renderScene={(route,navigator) =>
    // ...
  }
  navigationBar={
     <Navigator.NavigationBar  routeMapper={{ LeftButton: (route,navigator,index,navstate) => { return (<Text>Cancel</Text>); },RightButton: (route,index,navstate) => { return (<Text>Done</Text>); },Title: (route,navstate) => { return (<Text>Awesome Nav Bar</Text>); },}} style={{backgroundColor: 'gray'}} /> } />

NavigatorBar里设置了三个元素,左右两个按钮和中间的Title。上面代码中的按钮无法响应用户的点击操作。下面就看看如何添加这部分代码

LeftButton: (route,navstate) =>
  {
    if (route.index === 0) {
      return null;
    } else {
      return (
        <TouchableHighlight onPress={() => navigator.pop()}> <Text>Back</Text> </TouchableHighlight> ); } },

理论上如的部分就看到这里。我们看看我们的代码是怎么添加的:

var NavigationBarRouteMapper = {
    LeftButton(route,navstate) {
        if (index > 0) {
            return (
                <TouchableHighlight style={{ marginTop: 10 }} onPress={() => { if (index > 0) { navigator.pop(); } } }> <Text>Back</Text> </TouchableHighlight> ) } else { return null } },RightButton(route,navstate) { return null; },Title(route,navstate) { return ( <TouchableOpacity style={{ flex: 1,justifyContent: 'center' }}> <Text style={{ color: 'white',margin: 10,fontSize: 16 }}> Data Entry </Text> </TouchableOpacity> ); } };

在左侧按钮中,首先检查当前Scene的index是多少。如果是大于0的就说明可以回退到上一级,否则不作处理。

另外,给Title也添加了响应点击的代码。但是只是一个效果,没有添加onPress事件的处理代码

push & pop

总结一下上面的内容。需要跳转的HomeController和PetListController已经准备好了。导航用的Navigator也配置完成了,并且也包括NavigationBar。在绘制每一个Secne的时候,也给这些Scene传入了props,里面包含了Route对象和navigator对象。

有了上面的内容只是可以在运行起来的时候显示一个Scene:HomeController。于是,在HomeController的ListView里的Row绘制的时候添加TouchableHighLight并在相应事件里调用了Navigator的push方法跳转到下一个Scene。

_onPressRow(rowID: number) {
        this.props.navigator.push({
            title: 'Pets',component: PetListController,passprops: {}
        });
    }

push方法里传入的对象就是Route类型(基本就是类型这个概念)。这个对象指明要跳转的是哪个Scene,以及其他信息。

pop方法在上面的NavigationBar里的左侧按钮已经讲到。

最后

要完全的实现Navigation,需要用到Navigator和跳转的Scene(组件)。而把他们串联起来的是Route定义和作为props传入每个Scene的navigator对象。

代码

代码这里,可以同时支持Android和iOS。还没有整理,不过对于这个简单的例子来说正合适。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...