• 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} />
    }
    
    • push方法,push(route: any)。Navigator使用这个方法跳转到一个新的Scene。

    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, navigator, index, navState) =>
               { return (<Text>Done</Text>); },
             Title: (route, navigator, index, navState) =>
               { return (<Text>Awesome Nav Bar</Text>); },
           }}
           style={{backgroundColor: 'gray'}}
         />
      }
    />
    

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

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

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

    var NavigationBarRouteMapper = {
    	LeftButton(route, navigator, index, navState) {
    		if (index > 0) {
    			return (
    				<TouchableHighlight style={{ marginTop: 10 }} onPress={() => {
    					if (index > 0) {
    						navigator.pop();
    					}
    				} }>
    					<Text>Back</Text>
    				</TouchableHighlight>
    			)
    		} else {
    			return null
    		}
    	},
    
    	RightButton(route, navigator, index, navState) {
    		return null;
    	},
    
    	Title(route, navigator, index, 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。还没有整理,不过对于这个简单的例子来说正合适。

  • 相关阅读:
    Appium python环境搭建
    打印各种三角形 正方形
    python学习笔记之---多进程实例
    selenium简介及发展
    python使用selenium自动化实现126邮箱登录的简单实例
    使用多进程访问网址的实例
    python学习笔记之---多进程【理论】
    爬虫设置定时任务
    python中eval和exec用法比较
    python使用文件处理加函数的方式写ATM简易操作
  • 原文地址:https://www.cnblogs.com/sunshine-anycall/p/5958012.html
Copyright © 2020-2023  润新知