React Native的导航有两种,一种是iOS和Android通用的叫作Navigator,一种是支持iOS的叫作NavigatorIOS。咱们这里只讨论通用的Navigator。会了Navigator,NavigatorIOS也就不是什么难事了。react
本文所使用的是React Native 0.34。FB团队更新的太快了,我会在后续出现大的改动的时候更新本文以及代码。android
Navigator在不一样的Scene之间跳转。ios
initialRoute对象
这是Navigator所必须的,用于指定第一个Scene。git
renderScene(router: any, navigator: Navigator)
。renderScene
方法用来根据一个给定的route来绘制Scene。如:(route, navigator) => { <MySceneComponent title={route.title} navigator={navigator} /> }
push(route: any)
。Navigator使用这个方法跳转到一个新的Scene。API就了解这么多,下面看一个简单的例子。数据都是写死的。github
这个例子的主要功能就是从一个Scene(组件)HomeController,跳转到另外的一个组件PetListController。就是从一组用户里点选一个以后显示这个用户拥有的宠物列表。react-native
代码里的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> ); } };
文中储备要代码都已经略去。app
你能够看到,数据源就是一个数组['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
事件的处理代码。
总结一下上面的内容。须要跳转的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。尚未整理,不过对于这个简单的例子来讲正合适。