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。還沒有整理,不過對於這個簡單的例子來說正合適。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM