React Native填坑之旅--Navigation篇
React Native的导航有两种,一种是iOS和Android通用的叫做Navigator,一种是支持iOS的叫做NavigatorIOS。我们这里只讨论通用的Navigator。会了Navigator,NavigatorIOS也就不是什么难事了。
本文所使用的是React Native 0.34。FB团队更新的太快了,我会在后续出现大的改动的时候更新本文以及代码。
Navigator基础
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使用的组件。
NavigatorBar
上面的例子运行出来的时候有一个极大的问题,你不注意的话在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。还没有整理,不过对于这个简单的例子来说正合适。
React Native填坑之旅--Navigation篇的更多相关文章
- React Native填坑之旅--Flow篇(番外)
		flow不是React Native必会的技能,但是作为正式的产品开发优势很有必要掌握的技能之一.所以,算是RN填坑之旅系列的番外篇. Flow是一个静态的检查类型检查工具,设计之初的目的就是为了可以 ... 
- React Native填坑之旅--布局篇
		代码在这里: https://github.com/future-challenger/petshop/tree/master/client/petshop/src/controller 回头看看RN ... 
- React Native填坑之旅--ListView篇
		列表显示数据,基本什么应用都是必须.今天就来从浅到深的看看React Native的ListView怎么使用.笔者写作的时候RN版本是0.34. 最简单的 //@flow import React f ... 
- React Native填坑之旅--Button篇
		从React过来,发现React Native(以下简称RN)居然没有Button.隔壁的iOS是有UIButton的,隔壁的隔壁的Android里也是有的.没有Button,就没有点击效果啊.这还真 ... 
- React Native填坑之旅--LayoutAnimation篇
		比较精细的动画可以用Animated来控制.但是,在一些简单的界面切换.更新的时候所做的动画里再去计算开始值.结束值和插值器如何运作绝对是浪费时间. RN正好给我们提供了LayoutAnimation ... 
- React Native填坑之旅--与Native通信之iOS篇
		终于开始新一篇的填坑之旅了.RN厉害的一个地方就是RN可以和Native组件通信.这个Native组件包括native的库和自定义视图,我们今天主要设计的内容是native库方面的只是.自定义视图的使 ... 
- React Native填坑之旅--组件生命周期
		这次我们来填React Native生命周期的坑.这一点非常重要,需要有一个清晰的认识.如果你了解Android或者iOS的话,你会非常熟悉我们今天要说的的内容. 基本上一个React Native的 ... 
- React Native填坑之旅--重新认识RN
		如同黑夜里的一道光一样,就这么知道了F8. F8是每年一次Facebook每年一次的开发者大会.每次大会都会release相应的APP,iOS.Android都有.之前都是用Native开发的,但是2 ... 
- React Native填坑之旅--动画
		动画是提高用户体验不可缺少的一个元素.恰如其分的动画可以让用户更明确的感知当前的操作是什么. 无疑在使用React Native开发应用的时候也需要动画.这就需要知道RN都给我们提供了那些动画,和每个 ... 
随机推荐
- 开源GIS软件初探
			谈到GIS软件,首先让我们想到的便是GIS界的龙头大哥ESRI公司旗下的ArcGIS产品,从最初接触的version 9.2到如今的version 10.1,其发展可谓风生水起.MapInfo软件也不 ... 
- replaceWith() 和 replaceAll() 方法替换元素节点
			$("#Span1").replaceWith("<span title='replaceWith'>陶国荣</span>"); $(& ... 
- Delphi 中记录类型 给记录指针赋值。
			PPersion=^TPersion; TPersion=packed record Name:string; Sex:string; Clasee:string; end ... 
- quartz学习
			quartz是一个作业调度框架,用于指定工作(作业)在指定时间执行——定时工作. quartz的核心接口有: Scheduler接口:Scheduler是job的执行对象,用于工作的执行. Job接口 ... 
- 从红米手机经常发生UIM没有服务的一些猜想
			缘起:买了测试用的红米手机,安装电信卡,经常生UIM没有服务,大约两天1次. 我的解决办法:飞行模式切换一下就恢复正常. 之前这张卡用三星的信号是满格,红米断开挺经常的 上网搜索: 同样的现象,还好官 ... 
- svn各个图标代表什么意思
			最近参与公司项目开发要使用SVN,下面随笔记下在使用SVN中常见的图标各代表什么意思 灰色向右箭头:本地修改过 ,本地代码没有及时上库.灰色向右且中间有个加号的箭头:本地比SVN上多出的文件灰色向右且 ... 
- Android 获取当前时间问题1
			获取的写法如下: Calendar c = Calendar.getInstance();//可以对每个时间域单独修改 int year = c.get(Calendar.YEAR); int mon ... 
- [题解]vijos & codevs 能量项链
			a { text-decoration: none; font-family: "comic sans ms" } .math { color: gray; font-family ... 
- Java动手动脑(二)
			1>类的对象实例化 由于main为静态类型,所以在调用函数时也必须调用静态方法,如上代码中的求平方数的静态方法,如何在静态main中调用非静态类的方法呢? 静态方法只能直接访问静态成员,无法访问 ... 
- 编写serversocket简单示例1
			package j2se.core.net.tcp; import java.io.DataOutputStream;import java.io.IOException;import java.ne ... 
