跳到主要內容
版本:6.x

設定連結

在這個指南中,我們將設定 React 導航以處理外部連結。如果要

  1. 在 Android 和 iOS 的 React Native 應用程式中處理深度連結
  2. 在網路上使用時啟用瀏覽器的 URL 整合
  3. 使用 <Link />useLinkTo 使用路徑來導航。

請確定您已經在應用程式中 設定好深度連結 再繼續進行。如果您有 Android 或 iOS 應用程式,記得指定 prefixes 選項。

NavigationContainer 接受 linking 屬性,讓處理傳入的連結變得更輕鬆。您可以在 linking 屬性中指定的 2 個最重要的屬性為 prefixesconfig

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 導航會自動處理接收的連結。在 Android 和 iOS 上,在開啟 App 連結和在 App 開啟時接收新連結時,它會使用 React Native 的 Linking 模組 來處理接收的連結。在網路中,它會使用 歷史紀錄 API 同步瀏覽器的網址。

警告

目前似乎有程式錯誤 (facebook/react-native#25675),導致它在 Android 上永遠無法解決。我們加入了時間逾時以避免永遠卡住,但這表示在某些情況下可能無法處理連結。

你也可以傳遞 fallback 屬性給 NavigationContainer,以控制當 React 導航嘗試解析初始深度連結網址時要顯示的內容。

前綴

prefixes 選項可用於指定自訂 scheme(例如 mychat://),以及主機和網域名稱(例如 https://mychat.com),如果你已經組態 通用連結Android 應用程式連結

例如

const linking = {
prefixes: ['mychat://', 'https://mychat.com'],
};

注意 prefixes 選項不支援網路。主機和網域名稱會自動根據瀏覽器中的網站網址決定。如果你的應用程式只在網路中執行,則你可以從組態中省略此選項。

多個次網域

要符合關聯網域的所有次網域,你可以在特定網域開頭之前加上 * 來指定萬用字元。請注意,*.mychat.com 的輸入不會符合 mychat.com,因為星號後面有句號。要啟用 *.mychat.commychat.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'),
};

因為我們總是要處理頁面的網址,所以 Web 不支援此功能。

將路徑對應到路由名稱

如要處理連結,需要將其轉換為有效的 導航狀態,反之亦然。例如,路徑 /rooms/chat?user=jane 可能會轉換成如下所示的狀態物件

const state = {
routes: [
{
name: 'rooms',
state: {
routes: [
{
name: 'chat',
params: { user: 'jane' },
},
],
},
},
],
};

反應式導航預設會在解析網址時將路徑片段當作路由名稱。但是將路徑片段直接轉換為路由名稱可能不是預期的行為。

例如,你可能會想要將路徑 /feed/latest 解析成類似下列的內容

const state = {
routes: [
{
name: 'Chat',
params: {
sort: 'latest',
},
},
];
}

可以指定 linking 中的 config 選項,以控制深度連結要如何解析才能符合你的需要。

const config = {
screens: {
Chat: 'feed/:sort',
Profile: 'user',
},
};

在此,「聊天」是處理網址 /feed 的畫面名稱,「個人檔案」則是處理網址 /user 的畫面名稱。

接著可以將 config 選項傳遞給容器中的 linking prop

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 物件必須與你的應用程式的導航結構相符。例如,如果你在根目錄的導覽器中擁有「聊天」和「個人檔案」畫面,就是上述設定。

function App() {
return (
<Stack.Navigator>
<Stack.Screen name="Chat" component={ChatScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
}

如果你的「聊天」畫面在巢狀導覽器內,就必須考慮這一點。例如,考慮以下「個人檔案」畫面在根目錄,但「聊天」畫面巢狀在「首頁」中的結構

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',
},
};

類似地,任何巢狀結構都必須反映在設定中。有關更多詳細資訊,請參閱 處理巢狀導覽器

傳遞參數

要將參數傳遞給畫面以便傳遞部分資料是很常見的用例。例如,你可能要讓「個人檔案」畫面有 id 參數,以便知道它是哪個使用者的個人檔案。在處理深度連結時有可能透過網址將參數傳遞給畫面。

預設會解析查詢參數以取得畫面參數。例如,對於上述範例,網址 /user?id=wojciech 會將 id 參數傳遞給「個人檔案」畫面。

你也可以自訂如何從 URL 中剖析參數。假設你想要讓 URL 看起來像是 /user/wojciech,其中 id 參數是 wojciech,而不是讓 id 在查詢參數中。你可以為 path 指定 user/:id當路徑區段以 : 開頭時,它將被視為參數。例如,URL /user/wojciech 將解析成 Profile 畫面,其中字串 wojciechid 參數的值,並且將在 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 中有部分參數,亦即 /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',
},
},
},
};

在此設定中,你指定應為 users/:id 模式解析 Profile 畫面,且它巢狀在 Home 畫面內。然後剖析 users/jane 將產生以下狀態物件

const state = {
routes: [
{
name: 'Home',
state: {
routes: [
{
name: 'Profile',
params: { id: 'jane' },
},
],
},
},
],
};

重要的是要注意,狀態物件必須與巢狀導覽的階層相符。否則,狀態將會被捨棄。

處理不匹配的路線或 404

當應用程式開啟時,網址不正確,你大部分的時候會想顯示一個有包含部分資訊的錯誤畫面。在網頁上,這通常稱為 404 錯誤 - 或頁面找不到錯誤。

若要處理此事,你需要定義一個總攬路由,這表示如果沒有其他路線符合路徑,將會顯示此總攬路由。你可以透過為路徑比對模式指定 * 來達成。

例如

const config = {
screens: {
Home: {
initialRouteName: 'Feed',
screens: {
Profile: 'users/:id',
Settings: 'settings',
},
},
NotFound: '*',
},
};

在此,我們已定義一個名為 NotFound 的路線並將它設為比對 * 即所有內容的模式。如果路徑不符合 user/:idsettings,則此路線會比對路徑。

因此,像 /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 屬性來指定要使用在初始畫面的畫面。

在上述範例中,如果你想顯示 Feed 畫面為 Home 下領航員的初始路線,你的組態會長得像這樣

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 導覽的狀態。如果你的應用程式是在網路環境執行,瀏覽器的歷史紀錄將不會記錄此畫面,因為使用者從未造訪過此畫面。所以,如果使用者按下瀏覽器上的返回按鈕,畫面不會回到上一頁的畫面。

要注意的另一件事是,無法透過 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 屬性。profile 畫面指定路徑 users/:id,但由於它嵌套在路徑為 feed 的畫面內部,因此它會嘗試匹配模式 feed/users/:id

這將導致 URL /feed 導航到 Home 畫面,而 /feed/users/cal 導航到 Profile 畫面。

在此情況下,使用類似 /users/cal 而不是 /feed/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' },
},
];
}

在此,因為 React Navigation 不知道參數 date 應設定為時間戳記,因此將其剖析為字串,故為數字。您可以提供自訂函數來進行剖析,以自訂它

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',
};

假設您的 FeedProfile 畫面巢狀在 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 屬性,而且 FeedProfile 組態現在巢狀在 Home 下,以符合導覽結構。

如果您使用舊格式,它將會持續運作,而沒有任何變動。然而,您將無法指定萬用字元模式,來處理未對應的畫面或防止畫面被深入連結。舊格式會在下次重大版本中移除。因此,我們建議您可以在時將其移轉至新格式。

遊戲場

您可以在下方自訂設定檔和路徑,並查看路徑是如何解析的。

首頁
動態消息
個人資料
識別碼:"vergil"
設定

範例 App

在範例 App 中,您會使用 Expo Managed 工作流程。本指南將重點放在建立深度連結設定檔,而不是建立元件本身,但您隨時可以在 GitHub 儲存庫 中查看完整實作。

首先,您需要決定 App 的導航結構。為求簡潔,主導航器將成為具有兩個畫面的底部索引標籤導航器。它的第一個畫面將是一個稱為 HomeStack 的簡易堆疊導航器,其中包含兩個畫面:HomeProfile,第二個索引標籤畫面將只是一個沒有任何嵌套導航器的簡易畫面,稱為 Settings

BottomTabs
├── Stack (HomeStack)
│   ├── Home
│   └── Profile
└── Settings

建立導航結構後,您可以為深度連結建立設定檔,其中將包含每個畫面對應路徑區段的對應。例如

const config = {
screens: {
HomeStack: {
screens: {
Home: 'home',
Profile: 'user',
},
},
Settings: 'settings',
},
};

如您所見,HomeProfile 嵌套在 HomeStackscreens 屬性中。這表示當您傳遞 /home 網址時,它將解析為 HomeStack->Home 狀態物件(對於 /user 也是如此,它會是 HomeStack->Profile)。此物件中的嵌套應與導航器的嵌套相符。

在此,HomeStack 屬性包含一個設定檔物件。設定檔可以深入您想要的程度,例如:如果 Home 是導航器,您可以使用 screens 屬性將它設定為物件,並在其中放入更多畫面或導航器,讓網址字串更具可讀性。

如果希望特定的畫面作為導航器中的初始畫面,該怎麼辦?例如,如果您有一個會開啟 Home 畫面的網址,您希望能夠使用導航的 navigation.goBack() 方法從該畫面導航至 Profile。這可透過為導航器定義 initialRouteName 實現。它的外觀如下

const config = {
screens: {
HomeStack: {
initialRouteName: 'Profile',
screens: {
Home: 'home',
Profile: 'user',
},
},
Settings: 'settings',
},
};