LoginSignup
6

More than 1 year has passed since last update.

ExpoとReactNavigationのLinkの貼り方

Last updated at Posted at 2021-06-22

最初に

最近ReactNativeでexpoを使用して、開発する案件が増えているような気がしています。
ReactNativeアプリで以下のようなことしたいと思ったことはないでしょうか?

  • Webブラウザからリンクで直接アプリを開きたい。
  • アプリにpush通知を送った際に、push通知をタップされると任意のアプリのページに飛ばしたい。

上記のことはアプリ内にリンクを貼ることで実現できます。
今回はReactNative(expo)でのアプリ開発で重要であるアプリ内でのLinkの貼り方について解説します。

今回の目的

ReactNativeの画面の移動の方法としては、色々とあると思いますが、今回はReactNativeが公式で推奨している画面の移動方法のReactNavigationexpo-linkingというライブラリを使用してリンク移動できるようにします。

環境構築

$ expo init expo-tutorial
✔ Choose a template: › tabs (TypeScript)     several example screens and tabs using react-navigation and TypeScript
✔ Downloaded and extracted project files.

$ cd expo-tutorial
$ npm i
$ npm start

環境構築ですが、expo initを使用して新しいプロジェクトを作成します。
Choose a templateで色々と選択できるのですが、この際に上から3番目のtabs (TypeScript)を選択してください。
このtemplateを選択することで、すでにReactNavigationの設定及びLinkingの設定がされています。
npm startを実行するとブラウザでexpoが立ち上がり、QRコードからアプリを実行できます。

bottomTabTabOneからTabTwoへページの移動が移動できます。

アプリの解説

公式がすでにLinkまで貼ったアプリを作成してくれているので、
このアプリはどのような構造になっているのか解説することでLinkについて理解していきます。

Navigationの構造

まず、今回のNavigationの構造は以下のようになっています。

expo2.jpg

RootNavigationの中にBottomNavigationがあり、その中にさらにTabOneStackNavigation及びTabTwoStackNavigationが含まれています。
そしてそのTabOne 及び TabTwo StackNavigationの下に初めてTabOne及びTabTwoScreenがあります。

そのため、ユーザがTabOneScreenにアクセスするには次のフローを辿ります。

RootNavigation → BottomNavigation → TabOneStackNavigaiton → TabOneScreen

実際のコードを確認したいと思います。
必要なコードだけ一部抜粋して記載します。
主にNavigationのcomponentとname引数に注目してください。
nameはnavigationでページ移動する際に指定するものです。
例えば今回の場合は、

navigation.navigate("TabOneScreen")

と記述するとTabOneScreenに移動できたりします。

RootNavigation (navigation/index.ts)

function RootNavigator() {
  return (
    <Stack.Navigator screenOptions={{ headerShown: false }}>
      <Stack.Screen name="Root" component={BottomTabNavigator} />
      <Stack.Screen name="NotFound" component={NotFoundScreen} options={{ title: 'Oops!' }} />
    </Stack.Navigator>
  );
}

RootNavigationでBottomTabNavigationNotFoundScreenへの分岐があります。
BottomTabNavigationのnameはRoot
NotFoundScreenのnameはNotFound

BottomNavigation (navigation/BottomTabNavigation.tsx)

export default function BottomTabNavigator() {
  const colorScheme = useColorScheme();

  return (
    <BottomTab.Navigator
      initialRouteName="TabOne"
      tabBarOptions={{ activeTintColor: Colors[colorScheme].tint }}>
      <BottomTab.Screen
        name="TabOne"
        component={TabOneNavigator}
        options={{
          tabBarIcon: ({ color }) => <TabBarIcon name="ios-code" color={color} />,
        }}
      />
      <BottomTab.Screen
        name="TabTwo"
        component={TabTwoNavigator}
        options={{
          tabBarIcon: ({ color }) => <TabBarIcon name="ios-code" color={color} />,
        }}
      />
    </BottomTab.Navigator>
  );
}

次にBottomNavigatorからTabOneNavigatorTabTwoNavigatorへ分岐があります。
TabOneNavigatorのnameはTabOne
TabTwoNavigatorのnameはTabTwo
initialRootNameTabOneとあるので、defaultでBottomTabNavigatorまで到達した場合は、
TabOneNavigatorに処理が渡ります。

TabOneStackNavigation (navigation/BottomTabNavigation.tsx)

function TabOneNavigator() {
  return (
    <TabOneStack.Navigator>
      <TabOneStack.Screen
        name="TabOneScreen"
        component={TabOneScreen}
        options={{ headerTitle: 'Tab One Title' }}
      />
    </TabOneStack.Navigator>
  );
}

最後にTabOneNavigatorからTabOneScreenに処理が渡り、TabOneScreenが表示されます。
TabOneScreenのnameはTabOneScreen

TabTwoStackNavigation (navigation/BottomTabNavigation.tsx)

function TabTwoNavigator() {
  return (
    <TabTwoStack.Navigator>
      <TabTwoStack.Screen
        name="TabTwoScreen"
        component={TabTwoScreen}
        options={{ headerTitle: 'Tab Two Title' }}
      />
    </TabTwoStack.Navigator>
  );
}

TabOneNavigatorと同じ挙動を行います。

また、RootNavigationの中にはNotFoundScreenというものが存在しますが、
このScreenは普通にアプリを使用する分には、到達できないScreenとなっています。
詳しくはLinkのところで解説します。

リンクの構造

次にリンクの構造について解説します。

リンクについての記載がされているのは次のファイルになります。

navigation/LinkingConfiguration.ts

import * as Linking from 'expo-linking';

export default {
  prefixes: [Linking.makeUrl('/')],
  config: {
    screens: {
      Root: {
        screens: {
          TabOne: {
            screens: {
              TabOneScreen: 'one',
            },
          },
          TabTwo: {
            screens: {
              TabTwoScreen: 'two',
            },
          },
        },
      },
      NotFound: '*',
    },
  },
};

prefixesというのはアプリまでのURL(カスタムスキーマなどを含む)を記述するのですが、
Linking.makeUrlで現在のアプリの/までのURLを自動で作成してくれます。
※ Expo内で動作しているアプリやスタンドアロンで動いているアプリなど、環境によって、URLが変わってくるので、expoに頼って自動で作成するようにしましょう。

configにリンクの内容を記載しています。
よく観察すると、リンクの内容はNavigationと類似していることがわかります。

最初のscreensRootNotFoundがあり、
Root内のscreensTabOneTabTwoがあり、
TabOne内にTabOneScreenが記載されています。

Navigationにリンクを与える際は、Linkの内容とNavigationの内容が同期している必要があります。
screensにはNavigatorのcomponent名ではなくname名を記載します。

pathの記載

TabOneScreen:'one'とありますが、これがpathです。
Linking.makeUrl('/') + "one"でアプリ内でアクセスできます。
Expoで動作している場合は、次のURLをアプリ内のブラウザに入力してみてください。
exp://127.0.0.1:19001/--/one
もしくは、ターミナルから以下のコマンドを実行でも動作確認できます。(実機では確認不可)

npx uri-scheme open exp://127.0.0.1:19001/--/one --ios #andoridの場合は--androidを実行

expo3.jpg

pathのネストをする際は次のように記載します。

export default {
  prefixes: [Linking.makeUrl('/')],
  config: {
    screens: {
      Root: {
        path:"bottomTab",
        screens: {
          TabOne: {
            screens: {
              TabOneScreen: 'one',
            },
          },
          TabTwo: {
            screens: {
              TabTwoScreen: 'two',
            },
          },
        },
      },
      NotFound: '*',
    },
  },
};

次の場合はexp://127.0.0.1:19001/--/bottom/oneでアクセスできます。

リンクからデータを渡したいなどは、今回は説明しません。
次の公式ページを参考にしてください。

https://reactnavigation.org/docs/configuring-links/
https://docs.expo.io/guides/linking/#passing-data-to-your-app-through-the

リンク先が見つからない時

リンクで移動する場合は、リンク先が見つからない場合も考慮する必要があります。
そのための記述がNotFound:'*'です。
この記述を用意することで、リンク先が見つからなかった場合は、このNotFoundが呼びだされ
NotFoundScreenが表示されます。

Navigationにpathのconfigファイルを設定

上記で作成したPathを設定したファイルをNavigationに設定する必要があります。
それは以下のようにNavigationContainerlinkingに先程作成したファイルを指定することで実現できます。

navigation/index.ts

import LinkingConfiguration from './LinkingConfiguration'; //pathの設定ファイル
export default function Navigation({ colorScheme }: { colorScheme: ColorSchemeName }) {
  return (
    <NavigationContainer
      linking={LinkingConfiguration} // ここでNavigationに設定する
      theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
      <RootNavigator />
    </NavigationContainer>
  );
}

補足情報

ちょっと便利な情報を記載します。

LinkingConfigurationの型定義

デフォルトで作成されたものでは型定義が聞いていないため、使いにくいので、
pathのconfigファイルにも型定義を与え挙げると次のようなものになります。

navigation/LinkingConfiguration.ts

import * as Linking from 'expo-linking';
import { LinkingOptions } from "@react-navigation/native";


const LinkingConfiguration:LinkingOptions =  {
  prefixes: [Linking.makeUrl('/')],
  config: {
    screens: {
      Root: {
        screens: {
          TabOne: {
            screens: {
              TabOneScreen: 'one',
            },
          },
          TabTwo: {
            screens: {
              TabTwoScreen: 'two',
            },
          },
        },
      },
      NotFound: '*',
    },
  },
};

export default LinkingConfiguration

notificationとの連携

通知をタップしたときに好きなscreenにジャンプさせたいと思うときは
以下のように設定すると実行できます。

import * as Linking from 'expo-linking';
import { LinkingOptions } from "@react-navigation/native";


const LinkingConfiguration:LinkingOptions =  {
  prefixes: [Linking.makeUrl('/')],
  subscribe(listener) {
    const onReceiveURL = ({ url }: { url: string }) => {
      console.log(url);
      listener(url);
    };
    Linking.addEventListener("url", onReceiveURL);

    const notificationClickedListener = Notifications.addNotificationResponseReceivedListener(
      (response) => {
        /**
         * 通知をクリックした際の処理
         * dataのurlを取得する。
         * 例: data:{"url":"notification"}
         */
        const { url } = response.notification.request.content.data;
        if (typeof url === "string") {
          //redirectUrlを取得
          const redirectUrl = Linking.createURL(url);
          Linking.openURL(redirectUrl);
          return;
        }
        // rootにリダイレクト
        const redirectUrl = Linking.createURL("/");
        Linking.openURL(redirectUrl);
      }
    );
    return () => {
      /**
       * 後処理
       */
      Linking.removeEventListener("url", onReceiveURL);
      notificationClickedListener.remove();
    };
  },
  config: {
    screens: {
      Root: {
        screens: {
          TabOne: {
            screens: {
              TabOneScreen: 'one',
            },
          },
          TabTwo: {
            screens: {
              TabTwoScreen: 'two',
            },
          },
        },
      },
      NotFound: '*',
    },
  },
};

export default LinkingConfiguration

詳細については説明しませんが、push通知時に指定できるdataにJSON形式で{"url":"one"}といった感じで入力すると
通知をタップした際にTabOneScreenに移動することができます。

詳細は以下をご覧ください。
https://docs.expo.io/versions/latest/sdk/notifications/#handling-push-notifications-with-react-navigation

通知のテストは以下から簡単にできます。tokenの発行が入ります。
https://expo.io/notifications
token発行について
https://docs.expo.io/versions/latest/sdk/notifications/#fetching-tokens-for-push-notifications

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6