この記事は React Native Advent Calendar 2017 22日目です。
ReactNativeの開発において不可欠なナビゲーションライブラリ。
その主要なものの一つである react-native-router-flux (RNRF) は現在betaとしてv4が提供されています。
今回はそのv4が提供する、画面・ナビゲーションに関わるコンポーネント7つを、 https://github.com/aksonov/react-native-router-flux/blob/master/docs/API.md を見ながら足りない情報をコードから補いつつ順に見ていきます。
対象のコンポーネントはこちら。
- Scene
- Stack
- Tabs
- Modal
- Drawer
- Lightbox
- Overlay
(おさらい) RNRF の 画面遷移
v4になっても、画面遷移の基本は変わりません。
// 1. homeというkeyを持つ画面(Scene)に移動する
Actions.home()
// 2. 一つ前の画面に戻る
Actions.pop()
// 3. 今の画面を更新する
Actions.refresh()
ボタンなどをおした際のonPressなどに上記を仕込めば画面遷移させることができます。
なおこの先のサンプルコードにおいてcomponentに渡されるものは概ね以下のようなコンポーネントです。
import React, { Component } from 'react';
import { View, Text, StyleSheet } from "react-native";
import Button from "react-native-button";
import { Actions } from "react-native-router-flux";
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#F5FCFF",
},
});
export default class extends Component {
render() {
return (
<View style={styles.container}>
{/* タイトルはコンポーネント毎に編集し、またボタンの数やonPressの内容は適宜変更します */}
<Text>Home</Text>
<Button onPress={() => { Actions.profile() }}>next</Button>
</View>
);
}
}
こんなかんじ。
では下準備が終わったところで早速一つ一つ見ていきます。
Scene
The basic routing component for this router, all components require a key prop that must be unique. A parent cannot not have a component as a prop as it will act as a grouping component for its children.
ルーティングの基本となるcomponentです。
アプリ全体でユニークなkeyを必須で持ち、 たとえばhome
というkeyを持つSceneにはActions.home()
で遷移できます。
幾つかのSceneをグルーピングする親と、自身がcomponentを持つ子の2種類にわけられ、前者は自身が直接componentを持つことはありません。
<Router>
<Scene
key='main' // 親はcomponentを持たない
>
<Scene
key='login'
component={Login}
/>
<Scene
key='home'
initial={ true } // 隣接するsceneたちの中で最初に表示される
component={Home}
/>
<Scene
key='home'
component={Home}
/>
</Scene>
</Router>
動かすと...
Stack
A component to group Scenes together for its own stack based navigation. Using this will create a separate navigator for this stack, so expect two navbars to appear unless you add hideNavBar.
子Sceneをスタックさせる親コンポーネントです。
Stackで囲むと新しいスタックを設け、navbarを表示することができます。
navbarが不要であればSceneで囲ってあげるとよいです。
<Router>
<Scene
key='main'
>
<Scene
key='login'
component={Login}
/>
<Stack
key='inner'
>
<Scene
key='home'
component={Home}
/>
<Scene
key='profile'
component={Profile}
/>
</Stack>
</Scene>
</Router>
動かすと...
Stackの箇所で新たにnavbarが生じ、二重に表示されていることがわかります。
Stackを複数個並列にもち、その親Sceneには hideNavBar
をもたせる、といった構成はRNRFのサンプルコードにもでてきます。
Tabs
Can use all props listed above in as is syntatic sugar for .
<Tabs />
は <Scene tabs>
のシンタックスシュガーです。
自身は親Sceneであり、componentを直接は持たず、内包した子Sceneをタブで表示します。
RNRFはv4からreact-navigationの機構を採用しており、このコンポーネントはreact-nativagationのTabNavigatorが持つpropsを利用できます。
(なぜそのようになったのか / react-navigationとの違いはどこにあるのか、については https://github.com/aksonov/react-native-router-flux/issues/2001 ここが詳しいです)
react-navigation側の該当ドキュメントはこちら
https://reactnavigation.org/docs/navigators/tab#TabNavigatorConfig
上記URL先にprops一覧がありますが、冒頭に上げたRNRFのapiドキュメントにも記載があるのでそこだけ見るのでも良いかと思います。
<Router>
<Tabs
key='main'
swipeEnabled={ true } // スワイプでタブを移せる
animationEnabled={ false } // スワイプ時にアニメーションを付ける。個人的にはあまり好きでないのでfalseにしてオフっている
>
<Scene
key='login'
component={Login}
tabBarLabel='ログイン'
icon={ () => (<Text>ログイン</Text>)} // 本当はreact-native-vector-iconsなどをつかってアイコンを渡す...
/>
<Scene
key='home'
initial={ true } // 隣接するsceneで最初に表示される
component={Home}
tabBarLabel="ホーム"
/>
<Scene
key='home'
component={Home}
tabBarLabel="プロフィール"
/>
</Tabs>
</Router>
動かすと...
タブの色の変化が見づらいですね...ですが、SceneやStackとは異なりnabvarが登場せず、タブが移り変わっていることがわかるかと思います。
また swipeEnabled={ true }
にしたことでスワイプでタブ移動ができるようになっています。
ここまではつまづきも少ないですが、ここから少し複雑になります。
Modal
To implement a modal, you must use as the root scene in your Router. The Modal will render the first scene (should be your true root scene) normally, and all following To display a modal use as root renderer, so it will render the first element as normal scene and all others as popups (when they are pushed).
<Modal />
は <Scene modal>
のシンタックスシュガーです。
自身は親Sceneであり、componentを直接は持たず、内包した子Sceneを通常表示・モーダル表示します。
重要なのは、Modal直下最初のSceneのみ通常通り表示され、それ以外のSceneはpush時にmodalとして表示される点です。
なので、モーダル表示したいSceneはModalのとじタグ直前に並べておくことになります。
<Router>
<Scene
key="root"
hideNavBar={ true }>
<Modal
hideNavBar={ true }>
{/* Modal直下最初のsceneのみ通常表示 */}
<Scene
key='main'
>
<Scene
key='home'
component={Home}
/>
<Scene
key='profile'
component={Profile}
/>
</Scene>
{/* Modal直下二つ目以降はpush時にmodal表示される */}
<Scene
key='settings'
component={Settings}
/>
<Scene
key='error'
component={Error}
/>
</Modal>
{/* Modalの外なので関係なし */}
<Scene
key='login'
component={Login}
tabBarLabel='ログイン'
icon={ () => (<Text>ログイン</Text>)} // 本当はreact-native-vector-iconsなどをつかってアイコンを渡す...
/>
</Scene>
</Router>
動かすと...
Modal内2番目以降のSceneのみモーダル表示されていることが見て取れます。
LoginはModalの外なのでモーダルではなく通常の表示になっていますね。
Drawer
Can use all prop as listed in Scene as , syntatic sugar for
<Drawer />
は <Scene drawer>
のシンタックスシュガーです。
自身は親Sceneであり、同時にドロワー部分のcomponentを持ちます。
また、内包した子Sceneを通常表示します。
<Router>
<Scene
key="root"
hideNavBar={ true }>
<Drawer
key="drawer"
// drawerImage={() => (<Image/>)} // デフォルトのハンバーガーメニューを差し替える
// drawerIcon={() => (<Icon/>)} // デフォルトのハンバーガーメニューを差し替える
drawerWidth={ 300 }
contentComponent={DrawerContent} // ドロワー部分のcomponentを渡している
>
<Scene
key='login'
component={Login}
/>
<Scene
key='home'
component={Home}
/>
<Scene
key='profile'
component={Profile}
/>
</Drawer>
{/* Drawerの外なのでドロワーボタンは表示されない */}
<Scene
key='settings'
component={Settings}
/>
</Scene>
</Router>
動かすと...
ドロワーから遷移した先で Action.pop()
するとドロワーに戻るのが面白いですね。
Lightbox
Lightbox is a component used to render a component on top of the current Scene. Unlike modal, it will allow for resizing and transparency of the background.
<Lightbox />
は直前までに表示していた画面の上に覆いかぶさるように表示するコンポーネントです。
自身を透明にしたり、リサイズすることができます。
確認画面や簡単なエラーの表示、画像の簡易表示などに使うことが多いコンポーネントです。
Modalと似て、 Lightbox直下最初のSceneのみ通常通り表示され、それ以外のSceneはpush時にlightboxとして表示されます。
なので、lightboxとして表示したいSceneはLightboxのとじタグ直前に並べておくことになります。
さて、ここでLightbox用に簡単に新しいサンプルのコンポーネントを導入します。
背景が少し透明な黒色で、その中央にコンテンツを表示する、というスタイルを当てているだけで他に特殊なことはしていません。
// LightBoxBase.js
import React from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import { Actions } from 'react-native-router-flux';
const styles = StyleSheet.create({
shadow: {
backgroundColor: 'rgba(52,52,52,0.5)', // 透明な背景
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
justifyContent: 'center',
alignItems: 'center',
},
container: {
width: 300,
height: 300,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
},
});
const LightBoxBase = ({children}) => (
<View style={styles.shadow}>
<View style={styles.container} >
{children}
<Button title="Close"
onPress={() => { Actions.pop() }} // popさせてSceneを閉じる
/>
</View>
</View >
);
export default LightBoxBase;
これを利用してLightboxを試してみます。
const Error = () => (
<LightBoxBase>
<Text>error!</Text>
</LightBoxBase>
)
const Confirm = () => (
<LightBoxBase>
<Text>OK?</Text>
</LightBoxBase>
)
const Example = () => (
<Router>
<Lightbox
key="root"
>
{/* Modalと同様、Lightbox直下の最初のSceneのみ通常表示される */}
<Scene
key="inner">
<Scene
key='login'
component={Login}
/>
<Scene
key='home'
component={Home}
/>
</Scene>
{/* Modalと同様、Lightbox直下の2番目以降のSceneはpush時にLightboxの演出が加えられる */}
<Scene
key='error'
component={Error}
/>
<Scene
key='confirm'
component={Confirm}
/>
</Lightbox>
</Router>
);
動かすと...
背景となるSceneが画面中央寄せでわかりにくいですが、黒色半透明なSceneが覆いかぶさるように表示されていることが確認できました。
Overlay
API.mdに記述はないですが、 https://github.com/aksonov/react-native-router-flux/issues/2285 をみるに、
<Overlay />
は <Scene overlay>
のシンタックスシュガーのようです。
しかしoverlayの説明もドキュメントにはありません。
ExampleではRouterの直下に配置されており、気になるコンポーネントです。試しつつコードを眺めてみます。
<Router>
<Overlay
key="main">
<Scene
key='home'
component={Home}
/>
<Scene
key='profile'
component={Profile}
/>
</Overlay>
</Router>
このコードだとこんな感じ。
Overlay内にあるSceneが並んで同時に表示されています。
またコードを簡単に追ってみると
- overlay属性はOverlayNavitationを結局は作っており、
- OverlayNavigationではinitialのsceneを先頭にこさせるように並び替えた後、各sceneを並べて出力する
としているようにみえます。(もうちょっと丁寧に追う。)
また、イシューを見ると他ライブラリのDrawerと組みあせたいという場面でよく利用されており、サンプルコードではスナックバーを画面に重ね合わせて表示する際に使われています。
ということで、サンプルコードやIssue、コードををみるに、 2つ以上のSceneを同時に表示させたいとき にOverlayで囲っているようです。
RNRFのDrawer, Lightbox以外で、2つのSceneを同時に表示したいというケースにはOverlayを検討する...ととりあえず覚えて今後使ってみます。
RNRF(というかナビゲーション)のドキュメント、ちょっとわかりにくい => 小さく試す
様々なroutingライブラリを放浪した結果、個人的に最もシンプルに画面遷移を定義できると感じるRNRFに戻ってきました。
しかし、いかんせんRNRFはAPIのドキュメントが(以前よりはマシとは言え)揃いきっているとはいえず、また動きもコードやドキュメントだけでは把握しきれないところがあります。
幸いRNRFは手厚いサンプルコードを提供しており(そこも好き)、それを丁寧に追うことで使い方はみえてきますが、一方で手厚すぎて、所見ではウッ...と感じることもあるかと思います。
※ RNRFが提供するサンプルコードの中心となる箇所 ... 長い
https://github.com/aksonov/react-native-router-flux/blob/master/Example/Example.js
ということで今回はナビゲーションを提供するコンポーネントを一つ一つばらしてgifつきで見てみました。
皆様のReactNativeライフのお役に少しでも立てていれば幸いです。