在 React Native 裡面,大多的概念以及 Component 都相當好上手,但 Navigation 是個例外。Routing and Navigation 這個主題也被列在 Hard React Native Problems 這個 Repo 之中,其困難程度可見一斑。而這篇將簡單介紹這個概念跟內建的 Component Navigator
。
在前面篇章介紹到的 Demo App 都只有一個畫面,不過這極少在真實的 App 上發生,一但有了第二個畫面,就必須開始思考要如何做切換。Navigation 就是指在 App 上的畫面切換,概念可以對比到 Web 的 Routing,但又有些不同的地方,尤其可能會結合一些元件、動畫以及不同的使用者體驗操作,在 React Router 曾經對如何支援 React Native 有過激烈的討論。
Navigator
是用 JavaScript 實作的 Navigation 方案,因此能輕鬆的跨平台也能簡單的客製化樣式。也是這其中最老牌而且簡單易用的一個。
NavigatorIOS
是利用 iOS UIKit 的 UINavigationController
來實作的,所以有 Native 的感覺,不過因為只支援 iOS 所以價值不高。值得一提的是,wix 開發的 react-native-navigation 實作了跨平台的 Native Navigation,或許也是個有趣的選擇。
Navigator
跟 NavigatorIOS
都是 Stateful 的,NavigationExperimental
則是一個有別於它們的方式,是用 Reducer 來處理 Navigation State。因為在下一篇還會提到,這邊就先點到為止。
有興趣深入可以看看官方文件的介紹。
Scene 是指手機一個滿版的畫面,前面介紹的 Demo,例如 Day08 的出師表都只有一個 Scene。
先試著把出師表搬進 Navigator
裡面,我們必須設定讓 renderScene
回傳出師表的 JSX,並先放置一個 initialRoute
:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Navigator,
} from 'react-native';
export default class NavigatorExample extends Component {
render() {
return (
<Navigator
initialRoute={{ index: 0 }}
renderScene={(route, navigator) =>
<View style={styles.container}>
<Text style={styles.title}>出師表</Text>
<Text style={styles.paragraph}>
臣亮言:先帝創業未半,而中道崩殂。
今天下三分,益州疲弊,此誠危急存亡之秋也。
然侍衛之臣,不懈於內;忠志之士,忘身於外者,蓋追先帝之殊遇,欲報之於陛下也。
誠宜開張聖聽,以光先帝遺德,恢弘志士之氣;
</Text>
<Text style={styles.paragraph}>
不宜妄自菲薄,引喻失義,以塞忠諫之路也。
宮中府中,俱為一體,陟罰臧否,不宜異同。
若有作姦犯科,及為忠善者,宜付有司,論其刑賞,以昭陛下平明之治,不宜篇私,使內外異法也。
</Text>
</View>
}
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#EDE7C9',
padding: 20,
},
title: {
flex: 1,
paddingBottom: 5,
fontSize: 20,
},
paragraph: {
flex: 10,
backgroundColor: '#D25141',
color: '#F9CF7A',
fontSize: 24,
lineHeight: 30,
padding: 20,
},
});
可以先做出只有一個 Scene 的 Navigator,呈現畫面如下:
先試著加上第二篇文章雅量
。不過因為用到類似的結構就先把內文變成資料 Object:
const articles = [
{
index: 0,
title: '出師表',
paragraph1:
`臣亮言:先帝創業未半,而中道崩殂。
今天下三分,益州疲弊,此誠危急存亡之秋也。
然侍衛之臣,不懈於內;忠志之士,忘身於外者,蓋追先帝之殊遇,欲報之於陛下也。
誠宜開張聖聽,以光先帝遺德,恢弘志士之氣;`,
paragraph2:
`不宜妄自菲薄,引喻失義,以塞忠諫之路也。
宮中府中,俱為一體,陟罰臧否,不宜異同。
若有作姦犯科,及為忠善者,宜付有司,論其刑賞,以昭陛下平明之治,不宜篇私,使內外異法也。`,
},
{
index: 1,
title: '雅量',
paragraph1:
`朋友買了一件衣料,綠色的底子帶白色方格,當她拿給我們看時,一位對圍棋十分感與趣的同學說:
「啊,好像棋盤似的。」
「我看倒有點像稿紙。」我說。
「真像一塊塊綠豆糕。」一位外號叫「大食客」的同學緊接著說。`,
paragraph2:
`我們不禁哄堂大笑,同樣的一件衣料,每個人卻有不同的感覺。那位朋友連忙把衣料用紙包好,她覺得衣料就是衣料,不是棋盤,也不是稿紙,更不是綠豆糕。`,
}
];
export default class NavigatorExample extends Component {
render() {
return (
<Navigator
initialRoute={articles[1]}
renderScene={(route, navigator) =>
<View style={styles.container}>
<Text style={styles.title}>{route.title}</Text>
<Text style={styles.paragraph}>
{route.paragraph1}
</Text>
<Text style={styles.paragraph}>
{route.paragraph2}
</Text>
</View>
}
/>
);
}
}
暫時先讓 initialRoute
取用 articles[1]
的值,就可以看見雅量:
接下來,我們多加一個 Button
進來,然後在 onPress
的 Handler 上面判斷頁面來進行 navigator.push
或 navigator.pop
:
<Navigator
initialRoute={articles[0]}
initialRouteStack={articles}
renderScene={(route, navigator) =>
<View style={styles.container}>
<View style={styles.header}>
<Button
title="Switch"
onPress={() => {
if (route.index === 0) {
navigator.push(articles[1]);
} else {
navigator.pop();
}
}}
/>
<Text style={styles.title}>{route.title}</Text>
</View>
<Text style={styles.paragraph}>
{route.paragraph1}
</Text>
<Text style={styles.paragraph}>
{route.paragraph2}
</Text>
</View>
}
/>
navigator.push
跟 navigator.pop
時也會有不一樣的效果:
也可以用把 Navigator.NavigationBar
傳到 navigationBar
Prop 來達成一樣的功能,它可以直接指定 LeftButton
、RightButton
跟 Title
:
<Navigator
initialRoute={articles[0]}
initialRouteStack={articles}
renderScene={(route, navigator) =>
<View style={styles.container}>
<Text style={styles.paragraph}>
{route.paragraph1}
</Text>
<Text style={styles.paragraph}>
{route.paragraph2}
</Text>
</View>
}
navigationBar={
<Navigator.NavigationBar
routeMapper={{
LeftButton: (route, navigator, index, navState) =>
<Text onPress={() => {
if (route.index === 0) {
navigator.push(articles[1]);
} else {
navigator.pop();
}
}}>Switch</Text>,
RightButton: (route, navigator, index, navState) => null,
Title: (route, navigator, index, navState) =>
<Text style={styles.title}>{route.title}</Text>
}}
/>
}
/>
要改變動畫或是手勢的行為可以用 configureScene
這個 Prop 來達到,例如下面範例中的 FloatFromBottom
:
<Navigator
renderScene={(route, navigator) =>
// ...
}
configureScene={(route, routeStack) =>
Navigator.SceneConfigs.FloatFromBottom}
/>
Navigator.SceneConfigs
有以下的屬性可以拿來當作回傳值:
比較早期的 Library 有許多是建在 Navigator
之上,例如 Exponent 所做的 ExNavigator 或是 React Native Router Flux v2 以前的版本,不過漸漸的大部分正在遷移到 NavigationExperimental
。
就如同 Router 是複雜應用程式的入門磚,Navigator 一樣可以幫我們一窺複雜應用程式的樣貌,下一篇會繼續介紹這個主題,讓 Navigation 的輪廓能更完整。