リンクの構成
このガイドでは、外部リンクを処理するようにReact Navigationを構成します。これは、次の場合に必要です。
- AndroidとiOSのReact Nativeアプリでディープリンクを処理する
- Webで使用する場合にブラウザでURL統合を有効にする
- パスを使用して移動するには、
<Link />
またはuseLinkTo
を使用します。
先に進む前に、アプリでディープリンクを構成していることを確認してください。AndroidまたはiOSアプリをお持ちの場合は、prefixes
オプションを指定することを忘れないでください。
NavigationContainer
は、受信リンクの処理を容易にするlinking
プロップを受け入れます。linking
プロップで指定できる最も重要なプロパティの2つは、prefixes
とconfig
です。
import { NavigationContainer } from '@react-navigation/native';
const linking = {
prefixes: [
/* your linking prefixes */
],
config: {
/* configuration for matching screens with paths */
},
};
function App() {
return (
<NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
{/* content */}
</NavigationContainer>
);
}
linking
プロップを指定すると、React Navigationは受信リンクを自動的に処理します。AndroidとiOSでは、アプリがリンクで開かれた場合と、アプリが開いているときに新しいリンクを受信した場合の両方で、受信リンクを処理するためにReact NativeのLinking
モジュールを使用します。Webでは、ブラウザのURLと同期するためにHistory APIを使用します。
現在、Androidで解決されないバグ(facebook/react-native#25675)があるようです。永遠にスタックするのを防ぐためにタイムアウトを追加していますが、そのため、場合によってはリンクが処理されない可能性があります。
NavigationContainer
にfallback
プロップを渡すこともできます。これは、React Navigationが初期のディープリンクURLを解決しようとしているときに表示されるものを制御します。
プレフィックス
prefixes
オプションを使用して、ユニバーサルリンクまたはAndroidアプリリンクを構成している場合は、カスタムスキーム(例:mychat://
)とホスト&ドメイン名(例:https://mychat.com
)を指定できます。
例えば
const linking = {
prefixes: ['mychat://', 'https://mychat.com'],
};
prefixes
オプションはWebではサポートされていません。ホストとドメイン名は、ブラウザのWebサイトURLから自動的に決定されます。アプリがWebでのみ実行される場合は、構成からこのオプションを省略できます。
複数のサブドメイン
関連付けられたドメインのすべてのサブドメインと一致させるには、特定のドメインの先頭に*
を付けることでワイルドカードを指定できます。アスタリスクの後のピリオドのために、*.mychat.com
のエントリはmychat.com
と一致しません。*.mychat.com
とmychat.com
の両方に一致を有効にするには、それぞれについて個別のプレフィックスエントリを提供する必要があります。
const linking = {
prefixes: ['mychat://', 'https://mychat.com', 'https://*.mychat.com'],
};
特定のパスのフィルタリング
受信リンクをすべて処理したくない場合があります。たとえば、特定の画面に移動する代わりに、認証(例:expo-auth-session
)またはその他の目的のためのリンクをフィルターで除外する場合があります。
これを実現するために、filter
オプションを使用できます。
const linking = {
prefixes: ['mychat://', 'https://mychat.com'],
filter: (url) => !url.includes('+expo-auth-session'),
};
ページのURLを常に処理する必要があるため、これはWebではサポートされていません。
パスとルート名のマッピング
リンクを処理するには、有効なナビゲーション状態に翻訳する必要があります。たとえば、パス/rooms/chat?user=jane
は、次のような状態オブジェクトに変換できます。
const state = {
routes: [
{
name: 'rooms',
state: {
routes: [
{
name: 'chat',
params: { user: 'jane' },
},
],
},
},
],
};
デフォルトでは、React NavigationはURLを解析するときに、パスセグメントをルート名として使用します。しかし、パスセグメントをルート名に直接変換することは、期待される動作ではない可能性があります。
たとえば、パス/feed/latest
を次のように解析したい場合があります。
const state = {
routes: [
{
name: 'Chat',
params: {
sort: 'latest',
},
},
];
}
ニーズに合わせてディープリンクの解析方法を制御するために、linking
でconfig
オプションを指定できます。
const config = {
screens: {
Chat: 'feed/:sort',
Profile: 'user',
},
};
ここで、Chat
はURL/feed
を処理する画面の名前であり、Profile
はURL/user
を処理します。
configオプションは、コンテナのlinking
プロップで渡すことができます。
import { NavigationContainer } from '@react-navigation/native';
const config = {
screens: {
Chat: 'feed/:sort',
Profile: 'user',
},
};
const linking = {
prefixes: ['https://mychat.com', 'mychat://'],
config,
};
function App() {
return (
<NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
{/* content */}
</NavigationContainer>
);
}
configオブジェクトは、アプリのナビゲーション構造と一致する必要があります。たとえば、上記の構成は、ルートのナビゲーターにChat
とProfile
画面がある場合です。
function App() {
return (
<Stack.Navigator>
<Stack.Screen name="Chat" component={ChatScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
}
Chat
画面がネストされたナビゲーター内にある場合、それを考慮する必要があります。たとえば、Profile
画面がルートにあり、Chat
画面がHome
内にネストされている次の構造を考えてみます。
function App() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
}
function HomeScreen() {
return (
<Tab.Navigator>
<Tab.Screen name="Chat" component={ChatScreen} />
</Tab.Navigator>
);
}
上記の構造の場合、構成は次のようになります。
const config = {
screens: {
Home: {
screens: {
Chat: 'feed/:sort',
},
},
Profile: 'user',
},
};
同様に、ネストは構成に反映される必要があります。詳細については、ネストされたナビゲーターの処理を参照してください。
パラメーターの受け渡し
一般的なユースケースは、データを渡すために画面にパラメーターを渡すことです。たとえば、Profile
画面にid
パラメーターを持たせて、どのユーザーのプロファイルであるかを知りたい場合があります。ディープリンクを処理するときに、URLを介して画面にパラメーターを渡すことができます。
デフォルトでは、クエリパラメーターが解析されて、画面のパラメーターを取得します。たとえば、上記の例では、URL/user?id=wojciech
はid
パラメーターをProfile
画面に渡します。
URLからパラメーターを解析する方法をカスタマイズすることもできます。クエリパラメーターにid
を含めるのではなく、id
パラメーターがwojciech
であるURLを/user/wojciech
のようにしたいとします。これを行うには、path
にuser/:id
を指定します。**パスセグメントが:
で始まる場合、パラメーターとして扱われます。**たとえば、URL/user/wojciech
は、id
パラメーターの値として文字列wojciech
を持つProfile
画面に解決され、Profile
画面のroute.params.id
で使用できます。
デフォルトでは、すべてのパラメーターは文字列として扱われます。パラメーターを解析するための関数をparse
プロパティに、文字列に変換するための関数をstringify
プロパティに指定することで、解析方法をカスタマイズすることもできます。
/user/wojciech/settings
を解決して、パラメーター{ id: 'user-wojciech' section: 'settings' }
にする場合、Profile
の構成を次のようにすることができます。
const config = {
screens: {
Profile: {
path: 'user/:id/:section',
parse: {
id: (id) => `user-${id}`,
},
stringify: {
id: (id) => id.replace(/^user-/, ''),
},
},
},
};
これは次のような結果になります。
const state = {
routes: [
{
name: 'Profile',
params: { id: 'user-wojciech', section: 'settings' },
},
],
};
パラメーターをオプションとしてマークする
特定の条件によっては、URLにパラメーターが存在しない場合があります。たとえば、上記のシナリオでは、URLにsectionパラメーターがない場合もあります。つまり、/user/wojciech/settings
と/user/wojciech
の両方がProfile
画面に移動しますが、section
パラメーター(この場合は値がsettings
)が存在する場合と存在しない場合があります。
この場合、section
パラメーターをオプションとしてマークする必要があります。パラメーター名の後に?
サフィックスを追加して行うことができます。
const config = {
screens: {
Profile: {
path: 'user/:id/:section?',
parse: {
id: (id) => `user-${id}`,
},
stringify: {
id: (id) => id.replace(/^user-/, ''),
},
},
},
};
URL/users/wojciech
を使用すると、次のようになります。
const state = {
routes: [
{
name: 'Profile',
params: { id: 'user-wojciech' },
},
],
};
URLにsection
パラメーターが含まれている場合(例:/users/wojciech/settings
)、同じ構成で次のようになります。
const state = {
routes: [
{
name: 'Profile',
params: { id: 'user-wojciech', section: 'settings' },
},
],
};
ネストされたナビゲーターの処理
ターゲットナビゲーターがディープリンクの一部ではない他のナビゲーターにネストされている場合があります。たとえば、ナビゲーション構造が次のようになっているとします。
function Home() {
return (
<Tab.Navigator>
<Tab.Screen name="Profile" component={Profile} />
<Tab.Screen name="Feed" component={Feed} />
</Tab.Navigator>
);
}
function App() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
);
}
ルートにスタックナビゲーターがあり、ルートスタックのHome
画面内に、さまざまな画面を持つタブナビゲーターがあります。この構造では、パス/users/:id
をProfile
画面に移動したいとします。ネストされた構成は次のように表現できます。
const config = {
screens: {
Home: {
screens: {
Profile: 'users/:id',
},
},
},
};
この構成では、Profile
画面がusers/:id
パターンに対して解決されることを指定し、Home
画面内にネストされています。その後、users/jane
を解析すると、次の状態オブジェクトになります。
const state = {
routes: [
{
name: 'Home',
state: {
routes: [
{
name: 'Profile',
params: { id: 'jane' },
},
],
},
},
],
};
状態オブジェクトは、ネストされたナビゲーターの階層と一致する必要があることに注意してください。そうでないと、状態は破棄されます。
一致しないルートまたは404の処理
無効なURLでアプリが開かれた場合、ほとんどの場合、何らかの情報を含むエラーページを表示したいでしょう。Webでは、これは一般的に404エラー、つまりページが見つからないエラーとして知られています。
これを処理するには、パスと一致しない他のルートがレンダリングされるキャッチオールルートを定義する必要があります。パスの一致パターンに*
を指定して行うことができます。
例えば
const config = {
screens: {
Home: {
initialRouteName: 'Feed',
screens: {
Profile: 'users/:id',
Settings: 'settings',
},
},
NotFound: '*',
},
};
ここでは、NotFound
という名前のルートを定義し、*
(つまりすべて)に一致するように設定しています。パスがuser/:id
またはsettings
に一致しない場合、このルートによって一致するようになります。
そのため、/library
や/settings/notification
のようなパスは、次の状態オブジェクトに解決されます。
const state = {
routes: [{ name: 'NotFound' }],
};
さらに具体的な指定も可能です。たとえば、/settings
の下にある無効なパスに対して異なる画面を表示したい場合は、Settings
の下にそのようなパターンを指定できます。
const config = {
screens: {
Home: {
initialRouteName: 'Feed',
screens: {
Profile: 'users/:id',
Settings: {
path: 'settings',
screens: {
InvalidSettings: '*',
},
},
},
},
NotFound: '*',
},
};
この構成では、パス/settings/notification
は次の状態オブジェクトに解決されます。
const state = {
routes: [
{
name: 'Home',
state: {
index: 1,
routes: [
{ name: 'Feed' },
{
name: 'Settings',
state: {
routes: [
{ name: 'InvalidSettings', path: '/settings/notification' },
],
},
},
],
},
},
],
};
NotFound
画面に渡されるroute
には、ページを開いたパスに対応するpath
プロパティが含まれています。必要に応じて、このプロパティを使用して画面に表示される内容をカスタマイズできます(例:WebView
でページを読み込む)。
function NotFoundScreen({ route }) {
if (route.path) {
return <WebView source={{ uri: `https://mywebsite.com/${route.path}` }} />;
}
return <Text>This screen doesn't exist!</Text>;
}
サーバーサイドレンダリングを行う場合も、404エラーに対して正しいステータスコードを返す必要があります。処理方法については、サーバーサイドレンダリングのドキュメントを参照してください。
初期ルートのレンダリング
特定の画面を常にナビゲーターの状態の最初の画面として表示したい場合があります。initialRouteName
プロパティを使用して、最初の画面に使用する画面を指定できます。
上記の例では、Home
の下のナビゲーターでFeed
画面を最初のルートにしたい場合、構成は次のようになります。
const config = {
screens: {
Home: {
initialRouteName: 'Feed',
screens: {
Profile: 'users/:id',
Settings: 'settings',
},
},
},
};
すると、パス/users/42
は次の状態オブジェクトに解決されます。
const state = {
routes: [
{
name: 'Home',
state: {
index: 1,
routes: [
{ name: 'Feed' },
{
name: 'Profile',
params: { id: '42' },
},
],
},
},
],
};
initialRouteName
は、React Navigationの状態に画面を追加するだけです。アプリがWeb上で実行されている場合、ユーザーがまだアクセスしていないため、ブラウザーの履歴にはこの画面は含まれません。そのため、ユーザーがブラウザーの戻るボタンを押しても、この画面に戻りません。
もう1つ考慮すべき点は、URLを介して初期画面にパラメーターを渡すことができないということです。そのため、初期ルートにパラメーターが必要ないか、必要なパラメーターを渡すために画面構成でinitialParams
を指定してください。
この場合、URLのパラメーターは、パスパターンusers/:id
に一致するProfile
画面にのみ渡され、Feed
画面にはパラメーターは渡されません。Feed
画面にも同じパラメーターを含めたい場合は、カスタムgetStateFromPath
関数を指定して、それらのパラメーターをコピーできます。
同様に、子画面から親画面のパラメーターにアクセスしたい場合は、React Contextを使用して公開できます。
完全一致パスのマッチング
デフォルトでは、各画面に定義されたパスは、親画面のパスを基準としたURLに対して照合されます。次の構成を考えてみましょう。
const config = {
screens: {
Home: {
path: 'feed',
screens: {
Profile: 'users/:id',
},
},
},
};
ここでは、Home
画面と子画面であるProfile
画面の両方にpath
プロパティが定義されています。プロファイル画面はパスusers/:id
を指定していますが、パスfeed
を持つ画面の中にネストされているため、パターンfeed/users/:id
に一致しようとします。
これにより、URL/feed
はHome
画面に、/feed/users/cal
はProfile
画面に移動します。
この場合、/feed/users/cal
ではなく、/users/cal
のようなURLを使用してProfile
画面に移動する方が理にかなっています。これを実現するには、相対マッチングの動作をexact
マッチングにオーバーライドできます。
const config = {
screens: {
Home: {
path: 'feed',
screens: {
Profile: {
path: 'users/:id',
exact: true,
},
},
},
},
};
exact
プロパティをtrue
に設定すると、Profile
は親画面のpath
構成を無視し、users/cal
のようなURLを使用してProfile
に移動できます。
パスからの画面の省略
場合によっては、画面のルート名をパスに含めたくない場合があります。たとえば、Home
画面があり、ナビゲーション状態が次のようになっているとします。
const state = {
routes: [{ name: 'Home' }],
};
この状態を次の構成でパスにシリアル化すると、/home
になります。
const config = {
screens: {
Home: {
path: 'home',
screens: {
Profile: 'users/:id',
},
},
},
};
しかし、ホーム画面にアクセスする際にURLが/
だけであれば、より良いでしょう。パスとして空の文字列を指定するか、パスをまったく指定しないと、React Navigationはパスに画面を追加しません(パスに空の文字列を追加することと同じで、何も変更されません)。
const config = {
screens: {
Home: {
path: '',
screens: {
Profile: 'users/:id',
},
},
},
};
パラメーターのシリアル化と解析
URLは文字列であるため、ルートのパラメーターもパスを作成する際に文字列に変換されます。
たとえば、次のような状態があるとします。
const state = {
routes: [
{
name: 'Chat',
params: { at: 1589842744264 },
},
];
}
次の構成では、chat/1589842744264
に変換されます。
const config = {
screens: {
Chat: 'chat/:date',
},
};
このパスを解析すると、次の状態が得られます。
const state = {
routes: [
{
name: 'Chat',
params: { date: '1589842744264' },
},
];
}
ここで、date
パラメーターはタイムスタンプであるため数値のはずですが、React Navigationはそれを認識しないため、文字列として解析されました。解析に使用するカスタム関数を提供することで、これをカスタマイズできます。
const config = {
screens: {
Chat: {
path: 'chat/:date',
parse: {
date: Number,
},
},
},
};
パラメーターをシリアル化するカスタム関数も提供できます。たとえば、タイムスタンプの代わりにパスでDD-MM-YYYY形式を使用したいとします。
const config = {
screens: {
Chat: {
path: 'chat/:date',
parse: {
date: (date) => new Date(date).getTime(),
},
stringify: {
date: (date) => {
const d = new Date(date);
return d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate();
},
},
},
},
};
要件に応じて、この機能を使用して、より複雑なデータの解析と文字列化を行うことができます。
高度なケース
高度なケースによっては、マッピングを指定するだけでは不十分な場合があります。このようなケースを処理するには、URLを状態オブジェクトに解析するカスタム関数(getStateFromPath
)と、状態オブジェクトをURLにシリアル化するカスタム関数(getPathFromState
)を指定できます。
例
const linking = {
prefixes: ['https://mychat.com', 'mychat://'],
config: {
screens: {
Chat: 'feed/:sort',
},
},
getStateFromPath: (path, options) => {
// Return a state object here
// You can also reuse the default logic by importing `getStateFromPath` from `@react-navigation/native`
},
getPathFromState(state, config) {
// Return a path string here
// You can also reuse the default logic by importing `getPathFromState` from `@react-navigation/native`
},
};
構成の更新
以前のバージョンのReact Navigationでは、リンキングの構成形式が少し異なっていました。古い構成では、ネストされたナビゲーターに関係なく、オブジェクトに単純なキーと値のペアを許可していました。
const config = {
Home: 'home',
Feed: 'feed',
Profile: 'profile',
Settings: 'settings',
};
たとえば、Feed
画面とProfile
画面がHome
の中にネストされているとします。上記の構成ではそのようなネストがなくても、URLが/home/profile
であれば機能します。さらに、パスセグメントとルート名を同じように扱うため、構成に指定されていない画面にディープリンクすることもできます。たとえば、Home
の中にAlbums
画面がある場合、ディープリンク/home/Albums
はその画面に移動します。これは場合によっては望ましいかもしれませんが、特定の画面へのアクセスを防止する方法はありません。このアプローチでは、任意のルート名が有効なパスであるため、404画面のようなものを持つことも不可能です。
最新バージョンのReact Navigationでは、これとは異なる、より厳格な構成形式が使用されます。
- 構成の形状は、ナビゲーション構造のネストの形状と一致する必要があります。
- 構成で定義されている画面のみが、ディープリンクの対象となります。
そのため、上記の構成を次の形式にリファクタリングする必要があります。
const config = {
screens: {
Home: {
path: 'home',
screens: {
Feed: 'feed',
Profile: 'profile',
},
},
Settings: 'settings',
},
};
ここでは、構成オブジェクトに新しいscreens
プロパティが追加され、ナビゲーション構造に合わせてFeed
とProfile
の構成がHome
の下にネストされています。
古い形式を使用している場合でも、変更せずに機能し続けます。ただし、一致しない画面を処理するためのワイルドカードパターンを指定したり、画面をディープリンクから防ぐことはできません。古い形式は次のメジャーリリースで削除される予定です。そのため、可能な限り新しい形式に移行することをお勧めします。
プレイグラウンド
以下の構成とパスをカスタマイズして、パスがどのように解析されるかを確認できます。
ID | : | "vergil" |
サンプルアプリ
サンプルアプリでは、Expoのマネージドワークフローを使用します。このガイドでは、コンポーネントの作成ではなく、ディープリンキングの構成の作成に焦点を当てますが、githubリポジトリで完全な実装を確認できます。
最初に、アプリのナビゲーション構造を決定する必要があります。単純にするために、メインナビゲーターは、2つの画面を持つボトムタブナビゲーターになります。最初の画面は、Home
とProfile
という2つの画面を持つシンプルなスタックナビゲーターであるHomeStack
であり、2番目のタブ画面は、ネストされたナビゲーターのない単純な画面であるSettings
です。
BottomTabs
├── Stack (HomeStack)
│ ├── Home
│ └── Profile
└── Settings
ナビゲーション構造を作成した後、各画面をパスセグメントにマッピングするディープリンキングの構成を作成できます。たとえば
const config = {
screens: {
HomeStack: {
screens: {
Home: 'home',
Profile: 'user',
},
},
Settings: 'settings',
},
};
ご覧のように、Home
とProfile
はHomeStack
のscreens
プロパティにネストされています。つまり、/home
URLを渡すと、HomeStack
->Home
状態オブジェクトに解決されます(同様に、/user
はHomeStack
->Profile
になります)。このオブジェクトのネストは、ナビゲーターのネストと一致する必要があります。
ここで、HomeStack
プロパティには構成オブジェクトが含まれています。構成は必要に応じて深くすることができます。たとえば、Home
がナビゲーターの場合、screens
プロパティを持つオブジェクトを作成し、さらに画面やナビゲーターを内部に配置して、URL文字列を読みやすくすることができます。
ナビゲーターの最初の画面として特定の画面を使用したい場合を考えましょう。例えば、Home
画面を開くURLがあり、そこからナビゲーションのnavigation.goBack()
メソッドを使用してProfile
画面に移動したいとします。これは、ナビゲーターのinitialRouteName
を定義することで可能です。以下のように記述します。
const config = {
screens: {
HomeStack: {
initialRouteName: 'Profile',
screens: {
Home: 'home',
Profile: 'user',
},
},
Settings: 'settings',
},
};