前々回で、画面上にメッセージが出るだけのアプリが作成できましたが、全く動きがありません。そこで今回は画面の移動(画面遷移)を実現する方法として、ルート管理ライブラリであるreact-navigationを導入します。
この記事は連載です。親記事はこちら
前提
- Macを使います。でもWindows/Linuxでも同じで大丈夫なはず。
- React Nativeのブランクプロジェクトからスタートです。(前回までの内容の続きです。)
- React Navigation はV5を使います。
react-navigationの基本
React Nativeアプリは、Reactアプリと違ってURLベースのアプリケーションではないので、ReactのようなURLベースのルート管理(つまりグローバルのdocumentオブジェクトベースのルート管理)はできません。react-navigationでは、画面を親子兄弟の構造で設計しておいて、それをもとにnavigationオブジェクトを生成し、propsとして下位画面コンポーネントに渡して使うことで、ルート動作を実現していきます。
とりあえず、どんな動きができるかはこちらで見ることができます:https://react-navigation-example.netlify.app/
react-navigationをHello World!プロジェクトにインストール
前々回までに作ったHello World!プロジェクトをVS Codeで開きます。Ctrl + `
でTerminalを開いて、以下のコマンドで本体のreact-navigationと、react-navigationの動作に必要なライブラリを入れます。(詳しくはこちら)
$ npm install @react-navigation/native $ expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
さらに、画面履歴を管理するreact-navigation-stackを入れます。(詳しくはこちら)
$ npm install @react-navigation/stack
react-navigation-stack を使ってみる
Webブラウザでは、<a>
タグで示されたハイパーリンクをユーザーがクリックすると、画面がリンク先に遷移して、閲覧履歴にリンク先が追加されます。ここでユーザーが戻るボタンを押すと、元の画面に遷移して閲覧履歴から先のリンク先ページが削除(pop)されます。
React Nativeではこういった閲覧履歴の概念が存在せず、react-navigation-stack はまさにこの概念を導入するためのものです。
この動作を実現するには、画面を別個に認識することと、閲覧履歴を管理することが必要になります。それでは実際にHello World!に次の画面「Detail Screen」を作ってみましょう。Hello World!の下に、「Go to Details」ボタンを作って、ボタンを押したら「Detail Screen」に飛ぶようにします。
変更前
import { StatusBar } from "expo-status-bar"; import React from "react"; import { StyleSheet, Text, View } from "react-native"; export default function App() { console.log("Test message from Hello World!"); return ( <View style={styles.container}> <Text>Open up App.js to start working on your app!</Text> <Text>Hello World!</Text> <StatusBar style="auto" /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#fff", alignItems: "center", justifyContent: "center", }, });
変更後
import { StatusBar } from "expo-status-bar"; import React from "react"; import { StyleSheet, Button, Text, View } from "react-native"; import { NavigationContainer } from "@react-navigation/native"; import { createStackNavigator } from "@react-navigation/stack"; class HomeScreen extends React.Component { render() { return ( <View style={styles.container}> <Text>Open up App.js to start working on your app!</Text> <Text>Hello World!</Text> <Button title="Go to Details" onPress={() => this.props.navigation.navigate("Details")} /> <StatusBar style="auto" /> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#fff", alignItems: "center", justifyContent: "center", }, }); class DetailsScreen extends React.Component { render() { return ( <View style={styles.container}> <Text>Details Screen</Text> </View> ); } } const Stack = createStackNavigator(); function RootStack() { return ( <Stack.Navigator initialRouteName="Home" screenOptions={{ gestureEnabled: false }} > <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Details" component={DetailsScreen} /> </Stack.Navigator> ); } export default function App() { return ( <NavigationContainer> <RootStack /> </NavigationContainer> ); }
変更内容を順に見ていきましょう。
必要なコンポーネントをインポート
import { StyleSheet, Button, Text, View } from "react-native"; import { NavigationContainer } from "@react-navigation/native"; import { createStackNavigator } from "@react-navigation/stack";
createAppContainer
と createStackNavigator
が使えるようにimportしています。ついでにButtonも使えるようにしています。
初期画面をコンポーネント化
class HomeScreen extends React.Component { render() { return ( <View style={styles.container}> ... </View> ); } }
初期画面をreact-navigationに1つの「画面」として認識させるため、純粋な関数で表現されていた初期画面をHomeScreenと命名してReact Componentにコンポーネント化しています。これで、のちに出てくるcreateStackNavigator()に画面を渡すことができます。
ボタンを設置
<Button title="Go to Details" onPress={() => this.props.navigation.navigate("Details")} />
React Nativeなので、<a>
ではなく<Button>
コンポーネントを使ってリンクボタンを作ります。リンク先は、「Details」という名前の画面です。
Details Screenの画面コンポーネントを作成
class DetailsScreen extends React.Component { render() { return ( <View style={styles.container}> <Text>Details Screen</Text> </View> ); } }
初期画面同様にReact ComponentとしてDetails Screenの画面コンポーネントを作成します。このとき、コンポーネントの名前がDetailsではなくDetailsScreenであることに注意してください。先ほど初期画面(HomeScreen)ではDetailsに飛ぶようにButtonを作りました。ところがここで作ったコンポーネントの名前は違います。名前をつなぐ作業は、このあとのcreateStackNavigatorで行います。
Stack Navigatorを作成する
const Stack = createStackNavigator(); function RootStack() { return ( <Stack.Navigator initialRouteName="Home" screenOptions={{ gestureEnabled: false }} > <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Details" component={DetailsScreen} /> </Stack.Navigator> ); }
Stack Navigatorは、先に説明したように画面を認識して閲覧履歴を管理するオブジェクトです。
ここでは初期化情報を渡してStack Navigatorを作成しています。Stack.Navigatorという親コンポーネントに、Stack.Screenという子コンポーネントを内包する形で定義します。
Stack.Navigatorでは、初期画面がHomeであることを指定しています。
Stack.Screenでは、切り替えられる画面を定義しています。ここではHomeScreenコンポーネントをHomeという名前の画面で、DetailsScreenコンポーネントをDetailsという名前の画面で、登録しています。
作成したStack Navigatorは、RootStackに格納しました。
App本体としてStack Navigatorを登録
export default function App() { return ( <NavigationContainer> <RootStack /> </NavigationContainer> ); }
export defaultすると、App.jsのメインを指定することになります。
Navigationを動作させるためには、NavigationContainerコンポーネント内にStackを配置する必要があります。今回は先に作成したRootStackに画面全体を任せたいので、これをNavigationContainerに入れて、それをAppコンテナ化し、一番上のコンテナとして出力するように指定します。
実行確認
それでは変更を保存し、実行してみましょう。
$ expo start
シミュレーターでExpo Clientを実行すると、以下のようになります。
Go to Detailsをタップすると、次の画面に移ります。
画面移動にはちゃんとアニメーションもついていて、しかもなんと、自動的にBackボタンがついてますね!このBackボタン、もちろんちゃんと動作します。