設定連結
在這個指南中,我們將設定 React 導航以處理外部連結。如果要
請確定您已經在應用程式中 設定好深度連結 再繼續進行。如果您有 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 導航會自動處理接收的連結。在 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.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'),
};
因為我們總是要處理頁面的網址,所以 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
畫面,其中字串 wojciech
是 id
參數的值,並且將在 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/: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
屬性來指定要使用在初始畫面的畫面。
在上述範例中,如果你想顯示 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',
};
假設您的 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
下,以符合導覽結構。
如果您使用舊格式,它將會持續運作,而沒有任何變動。然而,您將無法指定萬用字元模式,來處理未對應的畫面或防止畫面被深入連結。舊格式會在下次重大版本中移除。因此,我們建議您可以在時將其移轉至新格式。
遊戲場
您可以在下方自訂設定檔和路徑,並查看路徑是如何解析的。
識別碼 | : | "vergil" |
範例 App
在範例 App 中,您會使用 Expo Managed 工作流程。本指南將重點放在建立深度連結設定檔,而不是建立元件本身,但您隨時可以在 GitHub 儲存庫 中查看完整實作。
首先,您需要決定 App 的導航結構。為求簡潔,主導航器將成為具有兩個畫面的底部索引標籤導航器。它的第一個畫面將是一個稱為 HomeStack
的簡易堆疊導航器,其中包含兩個畫面:Home
和 Profile
,第二個索引標籤畫面將只是一個沒有任何嵌套導航器的簡易畫面,稱為 Settings
BottomTabs
├── Stack (HomeStack)
│ ├── Home
│ └── Profile
└── Settings
建立導航結構後,您可以為深度連結建立設定檔,其中將包含每個畫面對應路徑區段的對應。例如
const config = {
screens: {
HomeStack: {
screens: {
Home: 'home',
Profile: 'user',
},
},
Settings: 'settings',
},
};
如您所見,Home
和 Profile
嵌套在 HomeStack
的 screens
屬性中。這表示當您傳遞 /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',
},
};