はじめに
この記事は React Native Advent Calendar 2019 10 日目の記事です。
昨日は @tkow さんの記事で、 React Native 0.60 で導入されたネイティブモジュールの autolinking に関する解説でした。
この記事は、 React Navigation の次バージョンである v5 について、機能や変更点などを予習しておこうというような内容です。
主な情報源は以下になります。
- GitHub の README とソースコード
- 公式ドキュメント(next)
- Expoのブログ
GitHub や公式ドキュメントは正式リリース後にリンク切れになる可能性があります。
v4 までのあらすじ
React Navigation は React Native 公式ドキュメントのナビゲーションのページ にも一番目に紹介されている、 React Native でナビゲーションを行う際の最もスタンダードなライブラリです。
ネイティブモジュールへの依存が比較的少なく、 Expo でも簡単に動かすことができるのが特徴です。
ここで、 React Navigation の今までの大まかな変遷を振り返ってみます。
beta (2017/02)
- 詳しい状況は知らないのですが、正式リリースまでの期間が長かったため beta だったにも関わらず本番投入されることが多い印象でした
v1 (2018/02)
- 今まで推奨されていた Redux 統合が deprecated になり、 react-navigation-redux-helpers として分離
- 人気のあった react-native-router-flux が v4 から
react-navigation
に依存するようになり、ナビゲーションライブラリの事実上のスタンダードとしての地位を確立
v2 (2018/05)
-
XNavigator(...)
が deprecated になり、代わりにcreateXNavigator(...)
を使うようになる -
TabBar
がcreateBottomTabBar
,createMaterialTopTabBar
,createMaterialBottomTabBar
に分割
v3 (2018/11)
- トップレベルのナビゲーションを
createAppContainer
で囲うように変更 - 内部的にナビゲーションの実装を
react-navigation-stack
,react-navigation-tabs
,react-navigation-drawer
パッケージに分離 - 途中で
react-navigation
パッケージに TypeScript の型情報追加
v4 (2019/09)
- v3 で分離されたパッケージが
react-navigation
を通して提供されなくなり、個別にインストールするように変更
メジャーバージョンが変わるごとに互換性のない変更があったものの、基本的にバージョンアップに必要な作業は関数名やパッケージ名の変更程度でした。
それでは、 v5 の変更点を見ていきましょう。
ナビゲーションを JSX で定義するようになり、動的に変化させることが可能になる
v4 以前
v4 までは、以下のように createXNavigator
関数ににオブジェクトを渡してナビゲーションを定義していました。 v1 では XNavigator
という名前の関数でしたが、基本は同じでした。
const StackNav = createStackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
Settings: { screen: SettingsScreen }
});
この createXNavigator
関数は render 内で呼んではならず、予め定義しておく必要がありました。そのため例えばニュースアプリ等で動的にカテゴリ一覧を取得してカテゴリ毎にタブを表示したい等といった場合は、ナビゲーションを表示する前に予めカテゴリ一覧を取ってきて、クラスコンポーネントのコンストラクタでナビゲーションを定義する等工夫する必要がありました。
v5
v5 では、以下のように render 内に JSX でナビゲーションを定義するようになります。 Web の React でよく使われる react-router
や前述の react-native-router-flux
に近い書き味です。
const Stack = createStackNavigator();
const StackNav = () => (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
);
createXNavigator
が Navigator
と Screen
というコンポーネントを返すので、それを使って定義します。 React.createContext
が Provider
と Consumer
を返す関係に似ています。記述量は v4 までに比べて若干増えてしまっています。
なんとこの書き方では通常のコンポーネントと同様に条件分岐やループが可能です。
const TabNav = () => (
const tabs = useState(['Tab1', 'Tab2']);
return (
<Tab.Navigator tabBarOptions={{ scrollEnabled: true }}>
{tabs.map(tab => (
<Tab.Screen key={tab} name={tab}>
{() => (
<View><Text>{tab}</Text></View>
)}
</Tab.Screen>
))}
</Tab.Navigator>
);
);
この例では、 tabs
を state として定義していますので、要素が増減するとナビゲーションもそれに追従します。
また、 この書き方のように Screen の中身を component
ではなく子コンポーネントに Render Callback として渡すことも可能です。こうすることで動的に props を渡すことが可能になりますが、 component
に渡した場合は不要な render が呼ばれないようにライブラリ側で最適化してくれるので、基本的には component
に渡すのが推奨なようです。 (READMEより)
オプションを render 内でセットできるようになる
v4 以前
v4 までは、 navigationOptions
も render 外で予め定義しておく必要がありました。コンポーネント側に設定する方法とナビゲーション側に設定する方法がありましたが、どちらにしてもコンポーネント内とデータをやりとりするには以下のように params を介してハンドラを渡す 必要がありました。
const EditScreen = props => {
const { navigation } = props;
const onSubmit = useCallback(...);
useEffect(() => {
// setParams する条件に気をつけないと無限に render されるので注意!!
if (onSubmit !== navigation.getParam('onSubmit')) {
navigation.setParams({ onSubmit });
}
}, [navigation, onSubmit]);
// ...
};
// コンポーネント側で設定する場合
EditScreen.navigationOptions = ({ navigation }) => ({
title: 'Edit',
headerRight: () => (
<Button onPress={navigation.getParam('onSubmit')}>
<Text>Submit</Text>
</Button>
)
});
const StackNav = createStackNavigator({
Edit: {
screen: EditScreen,
// ナビゲーション側で設定する場合
navigationOptions: ({ navigation }) => ({
title: 'Edit',
headerRight: () => (
<Button onPress={navigation.getParam('onSubmit')}>
<Text>Submit</Text>
</Button>
),
}),
},
// ...
});
v5
v5 では render 内で navigation.setOptions
を呼び出してオプションをセットする事が可能になります。
const EditScreen = props => {
const { navigation } = props;
const onSubmit = useCallback(...);
// コンポーネント内で設定する場合
navigation.setOptions({
title: 'Edit',
headerRight: () => (
<Button onPress={onSubmit}>
<Text>Submit</Text>
</Button>
)
});
// ...
};
const Stack = createStackNavigator();
const StackNav = () => (
<Stack.Navigator>
{/* ナビゲーション側で設定する場合 */}
<Stack.Screen name="Edit" component={Edit} options={{ ... }} />
</Stack.Navigator>
);
がっつりと副作用を起こしているように見えるのですがドキュメントを見る限り useEffect
の中ではなく render 直下に書くようです。パフォーマンス面までは検証が間に合いませんでした。
また、引き続きナビゲーション側でも options
として渡すことができますが、この場合は従来どおり getParam
を介する必要があるので、動的なオプションの場合コンポーネント内で setOptions
したほうが良さそうです。
TypeScript 対応強化
v4 以前
型の当て方は色々な方法がありますが、 v4 で関数コンポーネントとして定義する場合は以下のように書くとシンプルです。
type Params = {
id: string
};
type ScreenProps = {};
const DetailScreen: NavigationStackScreenComponent<
Params,
ScreenProps,
> = props => {
const { navigation } = props;
// 型推論が効く
const id = navigation.getParam('id');
const onPress = useCallback(() => {
// routeName は string, params は any
navigation.navigate('Comments', { id });
}, [navigation]);
// ...
};
そのコンポーネントがマウントされるスクリーンの params や screenProps を定義することはできましたが、他のスクリーンへの navigate のときに routeName や params に正しい型情報を与えるのは困難でした。
v5
ナビゲーション全体を表す ParamList という型を先に定義しておき、それを使う形に変更になります。
type StackParamList = {
Home: undefined;
Detail: { id: string };
Comments: { id: string };
};
type DetailScreenNavigationProp = StackNavigationProp<StackParamList, 'Detail'>;
type DetailScreenRouteProp = RouteProp<StackParamList, 'Detail'>;
type Props = {
navigation: DetailScreenNavigationProp;
route: DetailScreenRouteProp;
};
const DetailScreen: React.FC<Props> = props => {
const { navigation, route } = props;
// 型推論が効く
const { id } = route.params;
const onPress = useCallback(() => {
// routeName の型推論が効き、 params に id が必須になる
navigation.navigate('Comments', { id });
}, [navigation]);
// ...
}
navigation.navigate
に型推論が効くのは嬉しいです。ただし、ネストしたナビゲーションの場合 CompositeNavigationProp
を使って NavigationProp をマージしていくのですが、そちらで試したところ現在は navigation.navigate
の型推論が効きませんでした。
書き方としては画面ごとに NavigationProp と RouteProp を定義していくのは辛く今までのほうがシンプルに感じるので、今後の改善に期待です。
その他
-
createNativeStackNavigator
という、内部でネイティブのナビゲーションを使うナビゲーションが新しく追加されます - 現在は
react-navigation-hooks
を通して提供されている、useNavigation
などの Hooks を標準で提供するようになります - ひっそりと Web Support のページが追加されており、 今後
react-native-web
に対応することで Expo の Web 対応を強化していく方向性になると予想されます
まとめ
React Navigation v5 は react-navigation/navigation-ex リポジトリで開発中です。まだ alpha 段階で正式リリースまでは見たところ早くても数ヶ月はかかるかなという感じですし、 v4 との互換機能 も用意されているようなのでそこまでトラブルはさそうですが、このように変わるということだけでも知っておいて損はないかなと思いました。
今回手元で色々と試した内容を GitHub にプッシュしておりますので、興味があれば参考にしてみてください。
https://github.com/shinnoki/react-navigation-v5-example