NavigatorExperimentalの実装例を雑に読む
2016/05/29
こんなの書いてた。
でも、Facebook氏はNavigatorをメンテしないらしいわ。。
翻弄されてる感じが心地いいですね。
パッと見、NavigatorをFlux風に仕上げたモノって感じ。
F8AppでRedux使ってるし、NavigatorExperimentalは既存のNavigatorより理想な姿なのでしょう。
いきなり既存のNavigatorをNavigatorExperimentalに置き換えるのは情弱にとっては困難なので、小さい例で原理を学んでおきたい派。
dabit3/Navigator-Experimental-example
これを読む。
Flux,Reduxを知ってる人はすんなり理解できるはず。
Reduxでお馴染みのstate,reducer,actionが出てくる。
それぞれのオブジェクトの役目はこんな感じ
- state(コード例だとnavState)が画面のスタックを唯一管理するオブジェクト
- stateの中身を唯一操作するヤツがreducer(createReducerで作ってる関数)
- reducerに対して何かしらの処理を依頼をするオブジェクトがaction
Stateを初期化
Stateで画面のスタックを管理するので、アプリ起動したときに開くべき画面の情報などの初期化が必要になる。
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 |
function createReducer(initialState) { return (currentState = initialState, action) => { switch (action.type) { case 'push': return NavigationStateUtils.push(currentState, {key: action.key}); case 'pop': return currentState.index > 0 ? NavigationStateUtils.pop(currentState) : currentState; default: return currentState; } }; } const NavReducer = createReducer({ index: 0, key: 'App', children: [{key: 'Home'}] }) class RNExperimental extends Component { constructor(props) { super(props) this.state = { navState: NavReducer(undefined, {}) } } // blah blah blah... } |
NavReducer
にはcreateReducer(initialState)によって初期化されたReducerが渡されてる。
で、constructorの中でReducerに対してundefineと、空のActionである{}を渡してる。
createReducerの中身を見れば分かるが、undefineかつ{}を渡すとinitialStateが返ってくる。
つまり、navStateにinitialStateを渡してるってこと。
NavigationCardStackの初期化
このnavState
をViewスタック管理用のコンポーネントに渡す必要がある。
renderを見る。
1 2 3 4 5 6 7 8 |
render() { return ( <NavigationCardStack navigationState={this.state.navState} onNavigate={this._handleAction.bind(this)} renderScene={this._renderScene.bind(this)} /> ); } |
このNavigationCardStack
が旧Navigatorに変わるコンポーネントだ。
navigationStateとrenderSceneはisRequiredなので必ず何かしら渡さないとダメなんだけど、onNavigateは必須じゃないっぽい。
それからonNavigateの存在意義がよく分からない。
とりあえずrenderScene
に渡してるthis._renderScene
の処理を追ってみる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
_renderScene(props) { const ComponentToRender = this._renderRoute(props.scene.navigationState.key) return ( <ScrollView style={styles.scrollView}> {ComponentToRender} </ScrollView> ); } _renderRoute (key) { if (key === 'Home') return <Home onPress={this._handleAction.bind(this, { type: 'push', key: 'About' })} /> if (key === 'About') return <About goBack={this.handleBackAction.bind(this)} onPress={this._handleAction.bind(this, { type: 'push', key: 'Contact' })} /> if (key === 'Contact') return <Contact goBack={this.handleBackAction.bind(this) } /> } |
const ComponentToRender = this._renderRoute(props.scene.navigationState.key)
ってな感じでkeyに対応するコンポーネントを取り出して、それをScrollViewで包んで描画してますね。
ん、propsにsceneなんてなかったはず。
NavigationCardStackが内包するNavigationAnimatedViewの中を見てみると、sceneを渡してる。
navStateへのインタラクション
ある画面から、別の画面への遷移はどうやって実現するのか。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
_handleAction (action) { const newState = NavReducer(this.state.navState, action); if (newState === this.state.navState) { return false; } this.setState({ navState: newState }) return true; } handleBackAction() { return this._handleAction({ type: 'pop' }); } _renderRoute (key) { if (key === 'Home') return <Home onPress={this._handleAction.bind(this, { type: 'push', key: 'About' })} /> if (key === 'About') return <About goBack={this.handleBackAction.bind(this)} onPress={this._handleAction.bind(this, { type: 'push', key: 'Contact' })} /> if (key === 'Contact') return <Contact goBack={this.handleBackAction.bind(this) } /> } |
_renderRoute
で描画すべきコンポーネントを選出しながらonPress
プロパティに_handleAction
を渡している。
この_handleAction
の中でnavState
への処理を行ってる。
_handleAction
を見てみる。
NavReducerに現在のnavStateと、与えられたactionを渡すことで新しい状態newState
を生成してる。
それから、newStateとnavStateを比較し、両者が違うもの(違う画面)であればnewStateをnavStateとして更新する。
これによって画面が切り替わるわけだ。