ReactNativeで画面遷移するならNavigatorを使う
※Android寄りの内容
画面遷移は何かしらのGUIアプリを作る上で欠かせない。
『◯◯を押したら、詳細画面へ遷移する』とか…
『ユーザが◯◯な状態になったら、ベータ版の新機能を紹介する画面へ遷移する』とか…
『バックボタン押したら、前の画面に遷移する』とか…
いろんな場面で使う。
AndroidではstartActivity
で遷移先のクラス情報を含んだIntentを投げる。
iOSではStoryboardとか使ってイイカンジにやってる。しらんけど。
ReactNativeではNavigator
というコンポーネントを使って画面遷移を実現する。
NavigatorのドキュメントやNavigatorの解説記事を読めば終わりだけど、使ったメモを書いておく。
Navigator
に対してrouteオブジェクトなるモノを与えることで画面遷移処理が実行される。
routeオブジェクトなんて仰々しく名前ついてるけど単なるオブジェクトです。
Navigator
はrouteオブジェクトを受け取ると、renderScene
を呼ぶ。
このrenderScene
の中でどんなコンポーネントを表示するかというハンドリングを行うわけ。
Contents
ListView上のあるアイテムをタップしたら詳細画面へ遷移する(素朴なやり方)
頻出ケースですね。
ルートとなるコンポーネントでNavigator
を描画する。
renderScene
で描画するコンポーネントを素朴に場合分け。(ここ画面増えてきたらカオスになるよね)
各コンポーネント内からnavigator
の処理を呼び出せるようにプロパティとして渡しておく。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class ReactAwesome extends React.Component { render() { return ( <Navigator initialRoute={{ name: 'List' }} renderScene={this.renderScene} /> ); } renderScene(route, navigator) { if (route.name === 'List') { return <ClipsList navigator={navigator} /> } if (route.name == 'Detail') { return <ClipsDetail navigator={navigator} clip={route.clip}/> } } } |
ClipsList(ListViewを描画するコンポーネント)の一部を抜粋。
描画する各アイテム全体をタップ対象としたいので、ルートにTouchableHighlight
を置く。
onPress
で_navigateDetail
に処理を委譲する。
プロパティのNavigator
にルートオブジェクトをpushしてるだけ。
これでrenderScene
が呼び出されて、ClipsDetailが描画される。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
render() { return ( <ListView contentContainerStyle={styles.list} dataSource={this.state.items} renderRow={this.renderItem.bind(this)}/> ); } renderItem(item, sectionId, rowId) { return ( <TouchableHighlight onPress={() => this._navigateDetail(item)}> <View style={styles.container}> <Image source={{uri: item.photo}} style={styles.image}/> <View style={styles.textContainer}> <Text>{item.brand.name}</Text> </View> </View> </TouchableHighlight> ); } _navigateDetail(item) { this.props.navigator.push({ name: 'Detail', clip: item, }); } |
Androidの場合、バックボタン押したら前の画面、上の例だとListに戻ってほしい。
が、戻ることなく、アプリが閉じる。。
Androidでバックボタン押したら前に表示してた画面へ遷移する
BackAndroid
というコンポーネントを使って画面のスタックを操作する。
先述したNavigator
を描画するコンポーネント上でバックボタンのハンドリングをやってみる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
class ReactAwesome extends React.Component { componentDidMount() { BackAndroid.addEventListener('hardwareBackPress', this._backButtonHandler.bind(this)); } componentWillUnmount() { BackAndroid.removeEventListener('hardwareBackPress'); } render() { return ( <Navigator ref="navigator" initialRoute={{ name: 'List' }} renderScene={this.renderScene} /> ); } renderScene(route, navigator) { if (route.name === 'List') { return <ClipsList navigator={navigator} query='page=1'/> } if (route.name == 'Detail') { return <ClipsDetail navigator={navigator} clip={route.clip}/> } } _backButtonHandler() { var navigator = this.refs.navigator; if (navigator && navigator.getCurrentRoutes().length > 1) { navigator.pop(); return true; } return false; } } |
ライフサイクルに合わせてBackAndroid
にリスナーをつけたり、消したりしてる。
下記の実装例では、グローバルな空間でBackAndroid
へリスナーをつけて終わり。って感じだ。
https://github.com/iSimar/HackerNews-React-Native/blob/master/index.android.js
それからBackAndroid
のハンドラ、上の例でいうところの_backButtonHandler
内で自身のNavigator
を操作するためにref
で関連付けておく必要がある。
ここ、他に良い方法ないのかね。。
ListView上のあるアイテムをタップしたら詳細画面へ遷移する(少しスマートなやり方)
renderScene
の中でifだとswitchだの使って表示すべきコンポーネントを選択する、っていう素朴なやり方だと画面が増えてきたときに条件判定にまみれて困る。
条件判定を廃したやり方をメモしておく。
renderScene
を下記のように変更する。
1 2 3 4 |
renderScene(route, navigator) { let RouteComponent = route.component return <RouteComponent navigator={navigator} {...route.passProps} /> } |
routeオブジェクトに表示すべきコンポーネントの情報を突っ込んで、それを描画するって感じ。
これだとpushする側で適当なコンポーネントを詰めておけばいいのでrenderScene
側で条件判定する必要ない。
pushする側のコード例は下記のとおり。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// push時に渡すコンポーネントは予めインポートしておくこと。 import ClipsDetail from './ClipsDetail'; // blah blah blah... _navigateDetail(item) { this.props.navigator.push({ component: ClipsDetail, passProps: { clip: item, } }); } |
passProps
の中で定義したkey-valは、renderScene
の
1 |
return <RouteComponent navigator={navigator} {...route.passProps} /> |
...
によって展開されて渡される。
なので、描画されてるコンポーネント内でpassProps
の存在は気にしなくて良い。普通にthis.props.clip
とかイケる。
詳細はSpread Attributeを読めばOK。