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。尚未整理,不過對於這個簡單的例子來講正合適。