一. Hello Mobile Navigation
讓我們使用React Naviation來(lái)創(chuàng)建一個(gè)簡(jiǎn)單的聊天會(huì)話應(yīng)用纺念,可以跨平臺(tái)運(yùn)行在安卓和iOS客戶端羡鸥。
Setup and Installation- 配置和安裝
首先,確定你已經(jīng)配置好了React Native開(kāi)發(fā)環(huán)境鉴腻;然后迷扇,創(chuàng)建一個(gè)新的項(xiàng)目;安裝react-navigation包:
# Create a new React Native App
react-native init SimpleApp
cd SimpleApp
# Install the latest version of react-navigation from npm
npm install --save react-navigation
# Run the new app
react-native run-android # or:
react-native run-ios
檢查你的iOS或者安卓設(shè)備是否成功顯示出了ReactNative的初始App界面爽哎。
我們想要在iOS和Android平臺(tái)共享一份代碼蜓席,讓我們刪除index.ios.js
和index.android.js
文件的內(nèi)容,使用import './App';
的寫(xiě)法课锌。
現(xiàn)在為我們app的實(shí)現(xiàn)創(chuàng)建一個(gè)新的文件:App.js
厨内。
Introducing Stack Navigator -簡(jiǎn)介
對(duì)于我們的app,我們想要實(shí)現(xiàn)一個(gè)概念性的棧導(dǎo)航器渺贤,在這個(gè)導(dǎo)航器中雏胃,每一個(gè)新的場(chǎng)景組件都被放入到棧的頂部,并且返回時(shí)同樣移除棧頂?shù)哪莻€(gè)場(chǎng)景組件志鞍。我們可以使用StackNavigator瞭亮。讓我們從一個(gè)組件場(chǎng)景的情況開(kāi)始。
注意:場(chǎng)景組件既為一般的組件固棚,該組件被react-navigation渲染成一個(gè)路由頁(yè)面统翩。
import React from 'react';
import {
AppRegistry,
Text,
} from 'react-native';
import { StackNavigator } from 'react-navigation';
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
return <Text>Hello, Navigation!</Text>;
}
}
const SimpleApp = StackNavigator({
Home: { screen: HomeScreen },
});
AppRegistry.registerComponent('SimpleApp', () => SimpleApp);
這個(gè)場(chǎng)景的標(biāo)題
在static navigationOptions中是可以配置的仙蚜。那里有很多關(guān)于導(dǎo)航的選項(xiàng)都可以被配置。
現(xiàn)在同一個(gè)路由場(chǎng)景可以在iPhone和Android應(yīng)用中出現(xiàn)唆缴。
Adding a New Screen-添加一個(gè)新的組件場(chǎng)景
在App.js
文件中鳍征,讓我們添加一個(gè)新的組件場(chǎng)景,叫做ChatScreen
:
class ChatScreen extends React.Component {
static navigationOptions = {
title: 'Chat with Lucy',
};
render() {
return (
<View>
<Text>Chat with Lucy</Text>
</View>
);
}
}
我們?cè)?code>HomeScreen組件中添加一個(gè)按鈕面徽,使用Chat
的路由名稱(chēng)鏈接到ChatScreen
艳丛。
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => navigate('Chat')}
title="Chat with Lucy"
/>
</View>
);
}
}
我們使用的navigate函數(shù)來(lái)跳轉(zhuǎn)到ChatScreen
組件場(chǎng)景。該函數(shù)可以從當(dāng)前場(chǎng)景(HomeScreen)的navigation屬性中找到趟紊。但是想要實(shí)現(xiàn)跳轉(zhuǎn)氮双,我們必須將ChatScreen
組件添加到我們的StackNavigator
中:
const SimpleApp = StackNavigator({
Home: { screen: HomeScreen },
Chat: { screen: ChatScreen },
});
現(xiàn)在你可以使用navigate函數(shù)跳轉(zhuǎn)到一個(gè)新的組件場(chǎng)景,然后返回霎匈。
Passing params-傳遞參數(shù)
ChatScreen
組件場(chǎng)景強(qiáng)制固定所有參數(shù)的做法并不可取戴差,如果我們可以傳遞一個(gè)參數(shù)到ChatScreen
組件場(chǎng)景,這將比較有用铛嘱。讓我們開(kāi)始做吧暖释。在我們調(diào)用navigate函數(shù),添加一個(gè)指定的路由名字的時(shí)候墨吓,我們可以傳遞參數(shù)pasrams
到新的路由中球匕。首先,我們編輯HomeScreen組件場(chǎng)景帖烘,傳遞一個(gè)user的參數(shù)到新的路由亮曹。
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => navigate('Chat', { user: 'Lucy' })}
title="Chat with Lucy"
/>
</View>
);
}
}
我們可以編輯ChatScreen組件場(chǎng)景,使用路由傳過(guò)來(lái)的user參數(shù):
class ChatScreen extends React.Component {
// Nav options can be defined as a function of the screen's props:
static navigationOptions = ({ navigation }) => ({
title: `Chat with ${navigation.state.params.user}`,
});
render() {
// The screen's current route is passed in to `props.navigation.state`:
const { params } = this.props.navigation.state;
return (
<View>
<Text>Chat with {params.user}</Text>
</View>
);
}
}
現(xiàn)在你可以看到秘症,當(dāng)你使用navigate函數(shù)跳轉(zhuǎn)到ChatScreen路由頁(yè)面時(shí)傳遞的name參數(shù)照卦。嘗試修改HomeScreen組件場(chǎng)景,看看將會(huì)發(fā)生什么乡摹。
二. Nesting Navigators
由多種形式導(dǎo)航構(gòu)成的移動(dòng)應(yīng)用是很常見(jiàn)的役耕。在React Navigation中,路由和導(dǎo)航都是存在的聪廉,允許你為應(yīng)用構(gòu)建復(fù)雜的導(dǎo)航結(jié)構(gòu)蹄葱。在我們的聊天會(huì)話應(yīng)用中,我們想要在一個(gè)組件場(chǎng)景中放入一個(gè)tab標(biāo)簽锄列,來(lái)瀏覽最近的會(huì)話記錄或者所有的聯(lián)系人图云。
Introducing Tab Navigator-tab導(dǎo)航簡(jiǎn)介
讓我們?cè)贏pp.js中創(chuàng)建一個(gè)新的tab導(dǎo)航(TabNavigation)
:
import { TabNavigator } from "react-navigation";
class RecentChatsScreen extends React.Component {
render() {
return <Text>List of recent chats</Text>
}
}
class AllContactsScreen extends React.Component {
render() {
return <Text>List of all contacts</Text>
}
}
const MainScreenNavigator = TabNavigator({
Recent: { screen: RecentChatsScreen },
All: { screen: AllContactsScreen },
});
如果**MainScreenNavigator **場(chǎng)景組件作為導(dǎo)航的頂層組件,應(yīng)用將會(huì)看起來(lái)像這樣:
Nesting a Navigator in a screen-一個(gè)場(chǎng)景中嵌套一個(gè)導(dǎo)航
我們想要這些tab標(biāo)簽在應(yīng)用中的一個(gè)場(chǎng)景中出現(xiàn)邻邮,但是導(dǎo)航棧中新的場(chǎng)景可以覆蓋這些tab標(biāo)簽竣况。
讓我們將tabs導(dǎo)航作為一個(gè)頂層場(chǎng)景組件添加到我們之前創(chuàng)建的StackNavigator導(dǎo)航中。
const SimpleApp = StackNavigator({
Home: { screen: MainScreenNavigator },
Chat: { screen: ChatScreen },
});
由于MainScreenNavigator已經(jīng)成為SimpleApp
的一個(gè)場(chǎng)景組件筒严,我們可以設(shè)置navigationOptions
丹泉。
MainScreenNavigator.navigationOptions = {
title: 'My Chats',
};
注意:MainScreenNavigator本質(zhì)上是一個(gè)React組件類(lèi)情萤,可以設(shè)置靜態(tài)方法 navigationOptions。
讓我們給每一個(gè)tab標(biāo)簽也添加一個(gè)按鈕來(lái)鏈接到一個(gè)聊天會(huì)話場(chǎng)景摹恨。
<Button
onPress={() => this.props.navigation.navigate('Chat', { user: 'Lucy' })}
title="Chat with Lucy"
/>
現(xiàn)在我們已經(jīng)將一個(gè)導(dǎo)航器(navigator)嵌套到另外一個(gè)中筋岛。我們可以在導(dǎo)航器之間使用navigate方法。
注意: 導(dǎo)航器的分類(lèi) 晒哄、嵌套與跳轉(zhuǎn)
- 分類(lèi):StackNavigator為screen路由切換睁宰,TabNavigator為tab路由切換。
- 嵌套:StackNavigator與TabNavigator可以相互多層嵌套寝凌。
- 跳轉(zhuǎn):多層嵌套時(shí)柒傻,不同層級(jí)的組件場(chǎng)景都可以通過(guò)
navigate(title)
方法進(jìn)行跳轉(zhuǎn)。
三. Configuring the Header
只有StackNavigator導(dǎo)航系統(tǒng)支持設(shè)置頭部较木。
在之前的例子中红符,我們?cè)贏pp中創(chuàng)建了一個(gè)StackNavigation來(lái)展示幾個(gè)界面。
當(dāng)導(dǎo)航到一個(gè)會(huì)話界面的時(shí)候伐债,我們可以通過(guò)向navigate方法中傳遞參數(shù)來(lái)向新的路由傳遞预侯。在這個(gè)時(shí)候,我們希望在會(huì)話界面中提供人的名字(Lucy
)峰锁。
this.props.navigation.navigate('Chat', { user: 'Lucy' });
user
參數(shù)可以被會(huì)話界面所接收:
class ChatScreen extends React.Component {
render() {
const { params } = this.props.navigation.state;
return <Text>Chat with {params.user}</Text>;
}
}
Setting the Header Title - 設(shè)置頭部標(biāo)題
Next, the header title can be configured to use the screen param:
下面雌桑,可以使用參數(shù)來(lái)定義頭部標(biāo)題:
class ChatScreen extends React.Component {
static navigationOptions = ({ navigation }) => ({
title: `Chat with ${navigation.state.params.user}`,
});
...
}
Adding a Right Button - 添加一個(gè)右側(cè)按鈕
接下來(lái)我們添加一個(gè) 頭部導(dǎo)航選項(xiàng) 來(lái)允許我們添加一個(gè)定制的右側(cè)按鈕:
static navigationOptions = {
headerRight: <Button title="Info" />,
...
導(dǎo)航器選項(xiàng)可以根據(jù)導(dǎo)航器屬性來(lái)定義。讓我們根據(jù)路由參數(shù)來(lái)渲染一個(gè)不同的按鈕祖今,并且按鈕當(dāng)按下時(shí)執(zhí)行navigation.setParams
方法。
static navigationOptions = ({ navigation }) => {
const {state, setParams} = navigation;
const isInfo = state.params.mode === 'info';
const {user} = state.params;
return {
title: isInfo ? `${user}'s Contact Info` : `Chat with ${state.params.user}`,
headerRight: (
<Button
title={isInfo ? 'Done' : `${user}'s info`}
onPress={() => setParams({ mode: isInfo ? 'none' : 'info'})}
/>
),
};
};
現(xiàn)在拣技,頭部可以影響視圖的路由/狀態(tài):
看其余的頭部選項(xiàng)信息, 到頭部選項(xiàng)文檔千诬。
四. NavigationPlayground
App.js - StackNavigator路由棧
- StyleSheet定義最細(xì)的線
//這一常量定義了當(dāng)前平臺(tái)上的最細(xì)的寬度「嘟铮可以用作邊框或是兩個(gè)元素間的分隔線徐绑。
StyleSheet.hairlineWidth
- StackNavigator方法參數(shù)
const AppNavigator = StackNavigator({
...ExampleRoutes,
Index: {
screen: MainScreen,
},
}, {
//初始頁(yè)面: 如果不設(shè)置該項(xiàng),則顯示位置在第一個(gè)的頁(yè)面組件(SimpleStack)莫辨。
initialRouteName: 'Index',
//頭部什么時(shí)候重新渲染:
//float: 只有一個(gè)head頭部傲茄,頁(yè)面切換的時(shí)候有動(dòng)畫(huà),ios里常用沮榜。
//screen: 每一個(gè)屏幕都有一個(gè)頭部盘榨,安卓里常用。
//none: 不渲染head頭部(該AppNavigator路由就沒(méi)有頭部)
headerMode: 'none',
// 路由切換動(dòng)畫(huà)
// card: 使用安卓ios默認(rèn)切換方式蟆融。ios-左滑動(dòng)出現(xiàn)草巡,android-向上漸變。
// modal: 從屏幕底部向上彈出型酥,只在ios上生效山憨。
mode: Platform.OS === 'ios' ? 'modal' : 'card',
});
- 遍歷對(duì)象
//routeName表示key值
Object.keys(ExampleRoutes).map(
(routeName: string) =>{
//Todo
}
SimpleStack.js - StackNavigator路由棧
- ES6中的變量與字符串的拼接方法
//``內(nèi)部防止字符串和變量
//${}內(nèi)放變量查乒,${}外放字符串
//變量與變量之間沒(méi)有連接符(${}${});字符串與變量之間(${}字符串)沒(méi)有連接符
`${params.name}'s Profile!`;
`${navigation.state.params.name}'s Photos`;
//可以換行
`${navigation.state.params.mode === 'edit' ? 'Now Editing ' : '' }${navigation.state.params.name}'s Profile`
- 使用
navigate
方法可以推入哪些路由頁(yè)面郁竟。
AppNavigator = StackNavigator (
A: {screen: A},
B: {screen: B}
)
B = StackNavigator({
B1 : {screen: B1},
B2: {screen: B2},
B3: {screen: B3}
});
分析: ①同一級(jí)路由頁(yè)面之間可以使用navigator
方法跳轉(zhuǎn): A玛迄、B之間;B1棚亩、B2蓖议、B3之間。②不同級(jí)路由之間可以相互跳轉(zhuǎn)(不推薦這樣使用)蔑舞,A可以直接跳轉(zhuǎn)到B1拒担、B2、B3攻询;B1从撼、B2、B3可以直接跳轉(zhuǎn)到A钧栖。
注意: A.navigator(B) 與A.navigator(B1)的對(duì)比:因?yàn)锽路由棧的初始路由頁(yè)面是B1低零,所以?xún)烧叨际秋@示B1頁(yè)面;但是前者是A-B1的過(guò)程拯杠,而后者是A-B1-B1的過(guò)程掏婶。
- navigation.goBack(null) 返回到哪里
不管有多層嵌套,navigation.goBack(null)
都返回加載此頁(yè)面的地方潭陪。
即:navigation.goBack(null)
既有可能返回到當(dāng)前路由棧的上一個(gè)頁(yè)面雄妥,又可能返回多上一級(jí)路由棧。 - 一個(gè)路由棧嵌套在另一個(gè)路由棧中依溯,父路由棧展示子路由棧的時(shí)候如何隱藏頭部
//SimpleStack路由棧嵌套在AppNavigator路由棧中
//設(shè)置AppNavigator展示SimpleStack時(shí)無(wú)header頭部老厌。
//SimpleStack.navigationOptions這種寫(xiě)法與static navigationOptions寫(xiě)法對(duì)比
SimpleStack.navigationOptions = {
header: null
};
- React無(wú)狀態(tài)組件特點(diǎn)
- 不需要聲明類(lèi),可以避免大量的譬如extends或者constructor這樣的代碼黎炉;
- 不需要顯示聲明this關(guān)鍵字 枝秤,可以不用綁定this;
- 支持設(shè)置默認(rèn)的Props類(lèi)型與值慷嗜;
- 可以訪問(wèn)Prop和Context的
const Text = (props, context) =>
<p style={context}>props.children</p>;
Text.contextTypes = {
fontFamily: React.PropTypes.string
};
Text.propTypes = { children: React.PropTypes.string };
Text.defaultProps = { children: 'Hello World!' };
class App extends React.Component {
static childContextTypes = {
fontFamily: React.PropTypes.string
}
getChildContext() {
return {
fontFamily: 'Helvetica Neue'
};
}
render() {
return <Text>Hello World</Text>;
}
}
注意:我們會(huì)使用函數(shù)式無(wú)狀態(tài)組件淀弹,除非需要本地 state 或生命周期函數(shù)的場(chǎng)景 。無(wú)狀態(tài)組件輸出方式完全取決于兩個(gè)參數(shù)庆械。
擴(kuò)展: Context薇溃,React中隱藏的秘密!
SimpleTabs.js - TabNavigator路由棧
- 如何設(shè)置
TabNavigator
的被選中tab的顏色
const SimpleTabs = TabNavigator({
Home: {screen: MyHomeScreen},
People: {screen: MyPeopleScreen},
Chat: {screen: MyChatScreen},
Settings: {screen: MySettingsScreen}
}, {
tabBarOptions: {
//被選中tab的tintColor顏色值
activeTintColor: Platform.OS === 'ios' ? '#e91e63' : '#fff',
},
//tab在頁(yè)面的頂部(top)還是底部(bottom)
tabBarPosition: 'bottom',
//是否有切換動(dòng)畫(huà)
animationEnabled: true,
//是否可以手指滑動(dòng)切換
swipeEnabled: true,
});
- 如何配置每一個(gè)tab的一些信息
MySettingsScreen.navigationOptions = {
//設(shè)置tab文字
tabBarLabel: 'Settings',
//設(shè)置tab圖標(biāo)
tabBarIcon: ({ tintColor, focused }) => (
//tintyColor在被選中時(shí)和未被選中時(shí)顏色值不同
//focused是一個(gè)是否被選中的布爾值
<Ionicons
name={focused ? 'ios-settings' : 'ios-settings-outline'}
size={26}
style={{ color: tintColor }}
/>
),
};
Drawer.js - DrawerNavigator
- DrawerNavigator方法
const DrawerExample = DrawerNavigator({
Inbox: {screen: InboxScreen},
Drafts: {screen: DraftsScreen},
}, {
//初始展示項(xiàng)
initialRouteName: 'Drafts',
contentOptions: {
//被選中drawer的tintColor顏色值
activeTintColor: '#e91e63'
},
});
- DraftScreen組件
const DraftsScreen = ({ navigation }) => (
<MyNavScreen
banner={'Drafts Screen'}
navigation={navigation}
/>
);
DraftsScreen.navigationOptions = {
//drawer顯示的文字
drawerLabel: 'Drafts',
drawerIcon: ({ tintColor }) => (
//tintColor在被選中和未被選中時(shí)的顏色不同缭乘。
<MaterialIcons
name="drafts"
size={24}
style={{ color: tintColor }}
/>
),
};
- MyNavScreen組件
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView style={styles.container}>
<SampleText>{banner}</SampleText>
<Button
//注意:打開(kāi)drawer的方法
onPress={() => navigation.navigate('DrawerOpen')}
title="Open drawer"
/>
<Button
onPress={() => navigation.goBack(null)}
title="Go back"
/>
</ScrollView>
);
其他
- StackNavigator隱藏header的兩種寫(xiě)法的對(duì)比
//寫(xiě)法一
const ProfileNavigator = StackNavigator({
Home: {screen: MyHomeScreen},
Profile: {screen: MyProfileScreen},
}, {
//內(nèi)外層header都不渲染
navigationOptions: {header: null}
});
//寫(xiě)法二
const ProfileNavigator = StackNavigator({
Home: { screen: MyHomeScreen},
Profile: {screen: MyProfileScreen},
}, {
//內(nèi)層header不渲染
headerMode: 'none'
});
//寫(xiě)法三
ProfileNavigator.navigationOptions = {
//外層不渲染
header: null
}
**寫(xiě)法一: ** 設(shè)置MyHomeScreen
和MyProfileScreen
路由頁(yè)面沒(méi)有頭部痊焊。設(shè)置的是內(nèi)層和外層路由頁(yè)面都沒(méi)有頭部。 優(yōu)先級(jí)低于在MyHomeScreen
和MyProfileScreen
通過(guò) static navigationOptions
方法設(shè)置。
**寫(xiě)法二: ** 設(shè)置MyHomeScreen
和MyProfileScreen
路由頁(yè)面沒(méi)有頭部薄啥。設(shè)置的只是內(nèi)層路由頁(yè)面沒(méi)有頭部辕羽。
寫(xiě)法三: 表示ProfileNavigator
作為一個(gè)外層路由頁(yè)面時(shí)沒(méi)有頭部。
引申: note this changed starting beta9
Before:
{ header: { visible: false, }}
Now:
{ header: null,}