JavaScript
reactnative
reactnavigation

[ReactNavigation] Tabのタップ時の挙動をカスタマイズする

More than 1 year has passed since last update.

ReactNavigationを使ってアプリを開発していて、規模がそれなりに大きくなってくると
TabNavigatorやStackNavigatorを組み合わせていくケースがよく出てきます。

組み合わせはいくらでも効くので便利なのですが、
幾つかカスタマイズしていかないといけない部分もあるので、そのあたりを拾ってみんとてします。

環境

react: v16.0.0,
react-native: v0.50.4,
react-navigation: v1.0.0-beta.21

タブをタップしたときにinitialScreenに戻る

例えば次のような、2つのStackNavigatorがTabNavigatorの中に入っている画面構成があったとします。


MainTabNavigator
 - StackNavigatorA
  - Screen1
  - Screen2

 - StackNavigatorB

  - Screen3
  - Screen4

タブをタップして左右のタブが切り替わるのはもちろんですが、
さらにScreen2にいるときににStackAのタブをタップしたらScreen1に戻るのが期待する挙動でした。
StackBの場合も同様に。
この動作はデフォルトでは備わっていないので、自分で設定する必要があります。

TabNavigatorConfigの設定

タブをタップしたときの挙動はTabNavigatorConfigのtabBarComponentで設定できるので、
もしタップしたタブが現在のタブと同じで、かつinitialScreenじゃなければ画面を戻るような処理を
jumpToIndex関数内に追加します。

画面をinitialScreenに戻す処理はNavigationActionsによって操作します。
docsはコチラ

今回の場合はResetActionを行ってから、initialScreenに遷移するような書き方をすれば良いでしょう。

tabBarComponent.js
tabBarComponent: (props) => {
  return (
    <TabBarBottom
      {...props}
      jumpToIndex={(nextIndex) => {
        const prevIndex = props.navigation.state.index;
        const prevTab = props.navigation.state.routes[prevIndex];
        const nextTab = props.navigation.state.routes[nextIndex];

        if (prevIndex === nextIndex && prevTab.routes.length > 1) {
          const firstScreenName = nextTab.routes[0].routeName;
          props.navigation.dispatch(NavigationActions.reset({
            index: 0,
            actions: [
              NavigationActions.navigate({ routeName: firstScreenName })
            ]
          }));
        } else {
          props.navigation.navigate(nextTab.routeName); // 通常遷移
        }
      }}/>
  );
}

NavigationActionsは覚えていると便利かと思います。

開発中に困った部分

上記のように設定すれば良いということはすぐに分かったのですが、
しばらくはなかなかうまく動いてくれないケースがありました。

それはStackNavigatorをReact.Componentに包んで、そのComponentをTabNavigatorの
要素に設定していたとき。Navigatorをそのまま設定しておかないと、
TabNavigatorからStackNavigator内のスクリーンが参照されなくなる(タブのroutes以下にStackNavigatorのスクリーンが存在しなくなる)ためです。

ReactNavigationのdocsに倣って、React.Component内にnavigationOptionsをstaticで宣言しようとして包んでいましたが、Componentを引っ剥がすとちゃんと動いてくれました。

他の挙動も同様に

タブをタップしたときの挙動はだいたいjumpToIndex内をいじれば大丈夫なはず。
例えばタップしたときに画面内のFlatListを一番上までスクロールしたい場合は、
FlatListの参照をとってref.scrollToOffset({offset: 0, animated: true}) などと書いてあげれば良いです。

これらの挙動はユーザ体験的には行われて欲しいと感じるものだと思うので、
あらかじめ設定しておくと良いかもしれません。