配置链接
在本指南中,我们将配置 React Navigation 来处理外部链接。如果您想:
- 处理 Android 和 iOS 上的 React Native 应用程序中的深度链接
- 在 Web 上使用时启用浏览器中的 URL 集成
- 使用
<Link />
或useLinkTo
使用路径进行导航。
在继续之前,请确保您已在您的应用程序中配置深度链接。如果您有 Android 或 iOS 应用程序,请记住指定prefixes
选项。
NavigationContainer
接受一个linking
道具,它使处理传入链接变得更容易。您可以在linking
道具中指定的两个最重要的属性是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 上,它将使用History API来同步浏览器中的 URL。
目前似乎存在一个错误(facebook/react-native#25675),导致它在 Android 上永远无法解析。我们添加了一个超时来避免永远卡住,但这意味着在某些情况下链接可能无法处理。
您还可以将fallback
道具传递给NavigationContainer
,它控制在 React Navigation 尝试解析初始深度链接 URL 时显示的内容。
前缀
prefixes
选项可用于指定自定义方案(例如mychat://
)以及主机和域名(例如https://mychat.com
),如果您已配置通用链接或Android 应用程序链接。
例如
const linking = {
prefixes: ['mychat://', 'https://mychat.com'],
};
请注意,prefixes
选项在 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'),
};
这在 Web 上不受支持,因为我们始终需要处理页面的 URL。
将路径映射到路由名称
要处理链接,您需要将其转换为有效的 导航状态,反之亦然。例如,路径 /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 中解析参数的方式。假设您希望 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 中包含 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
属性来指定用于初始屏幕的屏幕。
在上面的示例中,如果您希望 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 Navigation 的状态中。如果您的应用程序在 Web 上运行,则浏览器历史记录中不会包含此屏幕,因为用户从未访问过它。因此,如果用户按下浏览器的后退按钮,它不会返回到此屏幕。
另一点需要注意的是,无法通过 URL 将参数传递给初始屏幕。因此,请确保您的初始路由不需要任何参数,或者在屏幕配置中指定 initialParams
来传递所需的参数。
在这种情况下,URL 中的任何参数都只传递给与路径模式 users/:id
匹配的 Profile
屏幕,而 Feed
屏幕不会接收任何参数。如果您希望 Feed
屏幕具有相同的参数,可以指定一个 自定义 getStateFromPath
函数 并复制这些参数。
类似地,如果您想从子屏幕访问父屏幕的参数,可以使用 React 上下文 来公开它们。
匹配精确路径
默认情况下,为每个屏幕定义的路径与相对于其父屏幕路径的 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
屏幕。
在这种情况下,使用类似 /users/cal
的 URL 而不是 /feed/users/cal
导航到 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 仓库 中查看完整的实现。
首先,您需要确定应用程序的导航结构。为了简单起见,主导航器将是底部选项卡导航器,包含两个屏幕。它的第一个屏幕将是一个简单的堆栈导航器,称为 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
URL 时,它将解析为 HomeStack
->Home
状态对象(类似地,对于 /user
,它将是 HomeStack
->Profile
)。此对象中的嵌套应与我们的导航器的嵌套匹配。
这里,HomeStack
属性包含一个配置对象。配置可以深入到您想要的任何程度,例如,如果 Home
是一个导航器,您可以将其设置为包含 screens
属性的对象,并在其中放置更多屏幕或导航器,从而使 URL 字符串更易读。
如果您想要将特定屏幕用作导航器中的初始屏幕怎么办?例如,如果您有一个 URL 会打开 Home
屏幕,您希望能够使用导航的 navigation.goBack()
方法从该屏幕导航到 Profile
。可以通过为导航器定义 initialRouteName
来实现。它将如下所示
const config = {
screens: {
HomeStack: {
initialRouteName: 'Profile',
screens: {
Home: 'home',
Profile: 'user',
},
},
Settings: 'settings',
},
};