React Native / React Navigation を Mobx と使う

Shunsuke Sawada

React Native で個人アプリを開発していて、ナビゲーションどうしようかなと。
デファクトになりつつあるという噂の React Navigation を使ってみた。
使ってみたが、 Mobx と同時に使うにはちょっと工夫が必要だったのでメモしておきます。

React Navigation

Github
https://github.com/react-community/react-navigation
公式ページ
https://reactnavigation.org/

React Mobx

https://github.com/mobxjs/mobx-react

問題点

React Native で登録されている画面は navigation prop を受け取れるのだけど、その子孫のコンポーネントには渡ってこない。バケツリレーで受け渡すのは微妙なので、そこで Mobx の出番となる。

それはそうなんだけど、どうやって navigation prop を Mobx で管理するのかしばらく悩んだ。

アイデア

トップレベルのナビゲーターは Navigation Containers と呼ばれ、このコンポーネントから navigation prop が受け渡される。なので、このトップレベルのナビゲーターを参照できれば良いと思う。

The built in navigators can automatically behave like top-level navigators when the navigation prop is missing. This functionality provides a transparent navigation container, which is where the top-level navigation prop comes from.

https://reactnavigation.org/docs/navigators/navigation-prop

Main.js

jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const MainNavigator = TabNavigator({
  ScreenA:  { screen: ComponentA },
  ScreenB:  { screen: ComponentB },
  ScreenC:  { screen: ComponentC },
});

@observer
class Main extends Component {

  stores: {
    navStore: ObservableNavStore,
  };

  render() {
    return (
      <Provider {...stores} >
        <MainNavigator ref={(nav) => { stores.navStore.setNavigator(nav); }} />
      </Provider>
    );
  }
}

export default Main;

Navigation Container を参照

ref オプションで Navigation Container が参照できるので、それを Mobx の observable なプロパティにセットしている。

ただ、デフォルトの observable では新しいオブジェクトを生成してしまうので、ただの参照にするように ref. モディファイヤーを使う。詳しくは下記のドキュメントで。

observable.ref: Disables automatic observable conversion, just creates an observable reference instead.

modifiers | MobX

ObservableNavStore.js

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// @flow

import { NavigationNavigator, NavigationActions } from 'react-navigation';
import { action, observable } from 'mobx';

class ObservableNavStore {

  @observable.ref navigation: NavigationActions = null;

  @action setNavigator(navigator: NavigationNavigator): void {
    // navigator is NavigationContainer
    // Store `navigation` in navStore for future use in child components.

    // eslint-disable-next-line no-underscore-dangle
    this.navigation = navigator._navigation;
  }

}

export const navStore = new ObservableNavStore();
export default ObservableNavStore;

NavigationNavigator / NavigationActions の import
ObservableNavStore の export は 型チェックのためなので、flow を使っていなければ不要です。

やっていることは navigation という observable なプロパティと setNavigator という関数を定義しているだけです。(MainNavigator の ref オプションで使用しているもの)

画面遷移

ProvidernavStore ストアを登録してあるので子孫のコンポーネントでもどこでも
@inject を使用して navigation prop をピックアップできます。
これで navigate メソッドが使えるので、あとは普通の React Navigation の使い方と同じ。

jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@inject('navStore') @observer
class ScreenB extends Component {

  static defaultProps = {
    navStore: null,
  };

  navigate(id: number) {
    this.props.navStore.navigation.navigate('ScreenC', { id });
  }

  render() {
    const id = 123;
    <View>
      <Button onPress={() => { this.navigate(id); }} title="See More" />
    </View>
  }
}

export default ScreenB;

感想

今のところはこれで問題なくナビゲーションできている。シンプルなので、しばらくこれで使ってみようと思う。問題が起きたら考える。

Shunsuke Sawada

おすすめの記事

webpackを使ってJSとCSSをコンパイルする(ES6 / Sass)
Turbolinks で Google adsense が正しく表示されない時の対処方法
5
RailsでGoogle mapsを使いこなすためのメモ 2 / 地図デザインのカスタマイズ
2