Product Pain 是 React Native 聆聽社群聲音的重要平台,而在上面 Navigator 是個知名的「Pain」,也催生了 Navigation RFC 與新的 Navigation API - NavigationExperimental
。這篇會稍微介紹它的特點以及它的用法。
NavigationExperimental
的設計主要有這些特點:
NavigationExperimental
使用單向的資料流,其中用了 Redux 的概念 Reducer 來處理 Navigation State這些都是相當重要的特點,在前面介紹 Navigator
時看得出來,Navigator
本身掌控了 Navigation State,並且有 NavigationBar
這樣的 View Component,還利用 Prop 來設定 Transition 動畫。
而 NavigationExperimental
不會有這些的限制,能有更多的可能性。
為了實作基本的 Navigation,我們將會有以下步驟:
首先,我們必須先把需要的 Component 和 Util Import 進來。參照官方文件的方式,我們這邊將使用 Destructuring 來把 NavigationExperimental
的 CardStack
跟 StateUtils
用 NavigationCardStack
、NavigationStateUtils
這兩個名字拆出來:
import React, { Component } from 'react';
import { NavigationExperimental } from 'react-native';
const {
CardStack: NavigationCardStack,
StateUtils: NavigationStateUtils,
} = NavigationExperimental;
先來定義初始 Navigation State,裡面有存現有的 Rotue Array 跟 Focus 的 index:
export default class NavigationExample extends Component {
state = {
navigationState: {
index: 1,
routes,
},
};
onNavigationChange = (type, route) => {
// ..
};
render() {
return (
<Text>先隨便放</Text>
);
}
}
至於上面看不到的 routes... 當然還是我們的出師表跟雅量:
import shortid from 'shortid';
const articles = [
{
key: shortid.generate(),
index: 0,
title: '出師表',
paragraph1:
`臣亮言:先帝創業未半,而中道崩殂。
今天下三分,益州疲弊,此誠危急存亡之秋也。
然侍衛之臣,不懈於內;忠志之士,忘身於外者,蓋追先帝之殊遇,欲報之於陛下也。
誠宜開張聖聽,以光先帝遺德,恢弘志士之氣;`,
paragraph2:
`不宜妄自菲薄,引喻失義,以塞忠諫之路也。
宮中府中,俱為一體,陟罰臧否,不宜異同。
若有作姦犯科,及為忠善者,宜付有司,論其刑賞,以昭陛下平明之治,不宜篇私,使內外異法也。`,
},
{
key: shortid.generate(),
index: 1,
title: '雅量',
paragraph1:
`朋友買了一件衣料,綠色的底子帶白色方格,當她拿給我們看時,一位對圍棋十分感與趣的同學說:
「啊,好像棋盤似的。」
「我看倒有點像稿紙。」我說。
「真像一塊塊綠豆糕。」一位外號叫「大食客」的同學緊接著說。`,
paragraph2:
`我們不禁哄堂大笑,同樣的一件衣料,每個人卻有不同的感覺。那位朋友連忙把衣料用紙包好,她覺得衣料就是衣料,不是棋盤,也不是稿紙,更不是綠豆糕。`,
}
];
以上這段用到了 shortid 這個用來產生短 id 的套件,因為 key
不能重複。
接下來我們要來實作 onNavigationChange
這個 Function,它會看 Action Type 是 push
還是 pop
,分別運用不同的 Reducer - NavigationStateUtils.push
跟 NavigationStateUtils.pop
來產生新的 State (它們的 Source Code 不難懂),如果 State 改變了則進行 setState
:
onNavigationChange = (type, route) => {
// 把 navigationState 從 State 抽出來
let { navigationState } = this.state;
switch (type) {
case 'push':
const newRoute = {
...route,
key: shortid.generate(),
};
// Push 一個 Route,key 必須是唯一的
// 用 NavigationStateUtils 的 Push Reducer 得到 Next State
navigationState = NavigationStateUtils.push(navigationState, newRoute);
break;
case 'pop':
// 用 NavigationStateUtils 的 Pop Reducer 得到 Next State
navigationState = NavigationStateUtils.pop(navigationState);
break;
}
// 如果有改變就 setState
if (this.state.navigationState !== navigationState) {
this.setState({ navigationState });
}
};
在這邊定義 Scene 跟上一篇定義 Scene 時沒有太大差別,只差在 route
還有 onPushRoute
、onPopRoute
這兩個 Handler 都是從 Props 傳進來:
class Scene extends Component {
render() {
const { route, onPushRoute, onPopRoute } = this.props;
return (
<View style={styles.container}>
<View style={styles.header}>
<View style={styles.button}>
<Button
title="Switch"
onPress={() => {
if (route.index === 0) {
onPushRoute(routes[1]);
} else {
onPopRoute();
}
}}
/>
</View>
<Text style={styles.title}>{route.title}</Text>
</View>
<Text style={styles.paragraph}>
{route.paragraph1}
</Text>
<Text style={styles.paragraph}>
{route.paragraph2}
</Text>
</View>
);
}
}
其他的要填入的內容也都來自 route
。
這步驟我們要建一個 Component 來放 NavigationCardStack
也一併 Render 前面定義的 Scene:
class MyNavigator extends Component {
onPushRoute = route => this.props.onNavigationChange('push', route);
onPopRoute = () => this.props.onNavigationChange('pop');
renderScene = sceneProps => {
return (
<Scene
route={sceneProps.scene.route}
onPushRoute={this.onPushRoute}
onPopRoute={this.onPopRoute}
/>
);
};
render() {
return (
<NavigationCardStack
onNavigateBack={this.onPopRoute}
navigationState={this.props.navigationState}
renderScene={this.renderScene}
/>
);
}
}
上面我們把 onNavigationChange
包裝成 onPushRoute
跟 onPopRoute
方便 Scene 去做操作。
最後再把之前先隨便放在頂層 Component NavigationExample
的 render Function 給換成回傳 MyNavigator
,記得傳 navigationState
跟 onNavigationChange
進去:
render() {
return (
<MyNavigator
navigationState={this.state.navigationState}
onNavigationChange={this.onNavigationChange}
/>
);
}
然後就變出一個跟上一篇很像的 App!
新一代的 React Native Navigation Library 大多都是基於 NavigationExperimental
來實作也能看出現在的趨勢,其中比較出名的有:
NavigationExperimental
不過因為 React Native 的發布週期極短,NavigationExperimental
又是比較新的 API,這些第三方的 Library 不一定有辦法跟得上這個速度 (例如這當下 React Native Router Flux 還在用 React Native v0.26 的 NavigationExperimental),而 ExNavigation 現在也已經在 Maintenance Mode,所以筆者推薦可以直接利用底層的 API 封裝成自己方便使用的方式就好。
不過在 ExNavigation
的 Readme 可以看到這段:
An important note about the future
"ExNavigation 2" will be called "react-navigation" and it will live on the reactjs organization. It is currently being built and scheduled for a beta release in January, 2017. A migration path from ExNavigation will be provided.
This means that ExNavigation is currently in maintenance mode -- we aren't actively adding new features unless we need them for our own projects, because further work is being directed towards react-navigation. Pull requests are still welcome.
所以或許我們可以期待明年一月的 ExNavigation 2
也就是 React 官方的 react-navigation
。
Navigation 是個困難且懸而未定的問題,雖不能一下就找到 Best Practice,但至少 NavigationExperimental
是個不錯的一步,從社群上開始棄 Navigator
採用 NavigationExperimental
也能略知一二。