ReactNative路由导航
React Native开发中,路由导航是应用架构的核心。Expo框架推出的 Expo Router 颠覆了传统方案,它基于文件系统自动生成路由,集成 React Navigation 的底层能力,同时提供零配置的开发体验。其优势包括:
- ✅ 基于文件路径的路由:目录结构即路由映射,减少样板代码
- ✅ 完整的TypeScript支持:类型安全的导航参数传递
- ✅ 深度链接无缝集成:自动处理URL解析与路由匹配
- ✅ 跨平台一致性:
iOS/Android/Web
统一导航逻辑
文件路径路由
Expo Router和Next、Nuxt类似,也是通过目录结构进行路由映射。这种设计的好处可以大大简化开发流程,所见即所得
基本结构
app/
├── (tabs)/ # 嵌套路由组
│ ├── index.tsx # 路径: /tabs
│ ├── home.tsx # 路径: /tabs/home
│ ├── _layout.tsx # tabs路由组布局
├── setting.tsx # 路径:/setting
├── detail/[id].tsx # 动态路由:/detail/123
├── _layout.tsx # 根布局配置
2
3
4
5
6
7
8
这里简单模拟了一个应用路由,通过注释可以简单的了解到对应的路由路径是如何映射的
静态路由
创建app/setting.tsx
文件自动注册/setting
路由
动态路由
创建app/detail/[id].tsx
文件,[]
包括的文件则是动态路由,[id]
包围的为参数名
如访问/detail/123
,在页面中就可以拿到当前的路由参数id=123
路由布局
以_layout.tsx
命名的文件为作为当前路由下的布局内容,如下结构:
app/
├── user/
│ ├── index.tsx
│ ├── _layout.tsx #user布局
├── setting.tsx
├── _layout.tsx #根布局
2
3
4
5
6
布局内容通常可以将路由进行配置,添加公共的布局等等,如根布局
export default function RootLayout() {
const [loaded, error] = useFonts({
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
...FontAwesome.font,
});
useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
}
}, [loaded]);
if (!loaded) return null;
return <RootLayoutNav />;
}
function RootLayoutNav() {
const colorScheme = useColorScheme();
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
<Stack.Screen name="user" options={{ headerShown: false }} />
</Stack>
</ThemeProvider>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
根布局在路由加载时加载了必要的字体文件,并且在根路由上对相关路由进行配置,也添加了ThemeProvider
方便所有的子路由使用上下文信息
当有嵌套路由时同样可以创建当前路由的布局文件_layout.tsx
export default function UserLayout() {
return (
<View style={{ height: 100, backgroundColor: "salmon" }}>
<Text>顶部</Text>
<Stack />
</View>
);
}
2
3
4
5
6
7
8
9
组合路由
以()
包围的路由名为组合路由,如下方(user)
就为组合路由
app/
├── (user)/
│ ├── index.tsx
│ ├── detail.tsx
│ ├── _layout.tsx
2
3
4
5
组合路由(user)
不会在路由路径中显示,当访问/index
、/detail
时将访问对应的(user)/index.tsx
和(user)/detail.tsx
文件
组合路由的作用就是可以对路由进行分类,做一些公共相关配置、或者布局,如当前user
级别的路由需要展示公共的头部信息,那么可以简单这么写
export default function UserLayout() {
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={{ height: 100, backgroundColor: "salmon" }}>
<Text
style={{
textAlign: "center",
lineHeight: 100,
textDecorationLine: "underline",
fontSize: 22,
}}
>
顶部
</Text>
</View>
<View style={{ flex: 1, backgroundColor: "#999" }}>
{/*这里将会是子路由显示区域*/}
<Stack
screenOptions={{
title: "用户",
headerShown: false,
}}
/>
</View>
</SafeAreaView>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
那么user级别的页面将会呈现这样效果:
stack路由
stack路由时应用程序导航的默认行为,导航以堆栈的形式进行屏幕切换
import { Stack } from "expo-router";
export default function StackLayout() {
return (
<Stack screenOptions={{
headerShown: false,
// 定义Stack的一些公共配置,如:头部怎么显示、左侧区域、右侧区域等等
}}>
{/* 定义某个具体Screen的行为,优先级高于Stack */}
<Stack.Screen name="user" options={{ headerShown: false }} />
</Stack>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
stack路由布局下每个页面都是一个完成的页面,独占一个屏幕。你可以通过Stack.Screen
定制某个具体页面的显示行为,不注册的会自动以默认的Stack
行为注册
tab路由
tab路由顾名思义就是以tab导航器的形式展示几个页面,常见的有底部tab栏、顶部tab栏等等
import { Link, Tabs } from "expo-router";
export default function TabLayout() {
const colorScheme = useColorScheme();
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? "light"].tint,
// 定义tab的公共行为
}}
>
{/* 单独定制 index tab的显示行为 */}
{/* 如:标题、右侧区域等等 */}
<Tabs.Screen
name="index"
options={{
title: "Tab One",
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
headerRight: () => (
<Link href="/modal" asChild>
<Pressable>
{({ pressed }) => (
<FontAwesome
name="info-circle"
size={25}
color={Colors[colorScheme ?? "light"].text}
style={{ marginRight: 15, opacity: pressed ? 0.5 : 1 }}
/>
)}
</Pressable>
</Link>
),
}}
/>
{/* 其他不定制的会自动以默认tab的形式注册 */}
</Tabs>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
定义tab布局的路由后,会自动将当前路径下的所有文件以tab的形式注册
特殊路由
以 +
开头的路由对Expo Router有特殊的意义,有特定的用途
+not-found
:它捕获任何与应用中的路由不匹配的请求
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: 'Oops!' }} />
<View style={styles.container}>
<Text style={styles.title}>This screen doesn't exist.</Text>
<Link href="/" style={styles.link}>
<Text style={styles.linkText}>Go to home screen!</Text>
</Link>
</View>
</>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
+html
:用于自定义应用在web上使用的html样板
export default function Root({ children }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<ScrollViewStyleReset />
{/* 自定义头部等等... */}
</head>
<body>{children}</body>
</html>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+native-intent
:用于处理应用中与特定路由不匹配的深层链接,比如由第三方服务生成的链接
import ThirdPartyService from 'third-party-sdk';
export function redirectSystemPath({ path, initial }) {
try {
if (initial) {
// Detection of third-party URLs will change based on the provider
if (url.hostname === '<third-party-provider-hostname>') {
return ThirdPartyService.processReferringUrl(url).catch(() => {
// Something went wrong
return '/unexpected-error';
});
}
return path;
}
return path;
} catch {
return '/unexpected-error';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
其他
除了上面介绍的Stack
、Tab
风格的路由外,还有Modal
、Drawer
等几种常见的路由功能,感兴趣的可以参考官方文档
路由跳转
项目中基于文件的路由注册好后,就需要在不同页面间进行导航了,怎么跳转?以什么形式跳转?
Link
在Expo Router中链接到页面的典型方式是使用像web应用程序一样的链接。Expo Router有一个Link
组件,用于在页面之间导航,其中的href
属性可以直接定义目标路径
<Link href="/user">用户</Link>
默认情况下,Link
只能包含文本组件。你可以在带有 asChild
属性的链接中使用P
ressable或其他支持
onPress和
onClick`属性的组件
<Link href="/user" asChild>
<Pressable>
<Text>Home</Text>
</Pressable>
</Link>
2
3
4
5
除了直接传字符串路由外,也可以通过对象属性的形式传递。如这里通过路由名字
和路由参数
信息导航到动态路由
<Link
href={{
pathname: '/user/[id]',
params: { id: 'bacon' }
}}
>
View user (id in params in href)
</Link>
2
3
4
5
6
7
8
Redirect
redirect
组件用来重定向页面路径的,常见的场景是不符合某个条件时重定向到其他页面,如:没有登录时
export default function UserDetail() {
if (!isLogin) return <Redirect href="/login" />;
return <View>User Detail</View>
}
2
3
4
5
hooks
除了使用提供的组件外,最简单的方式就是一些hooks了。就像在React导航中一样,你可以从onPress
处理程序中调用一个函数来导航到另一个页面。在Expo Router中,你可以使用useRouter
钩子来访问导航函数
import { useRouter } from 'expo-router';
export default function Home() {
const router = useRouter();
return <Button onPress={() => router.navigate('/about')}>
Go to About
</Button>;
}
2
3
4
5
6
7
8
9
除此之外,还提供了push
、replace
、back
等等方法,点及 useRouter 查看更多使用信息
对于动态路由可以通过useLocalSearchParams
获取路由参数
const { id, limit } = useLocalSearchParams();
DeepLink
深度链接是指URL打开应用中的特定页面。在这个过程中,用户不仅被引导打开你的应用程序,而且他们被带到应用程序中的特定屏幕,这对于分享应用程序中特定页面的链接尤其有用
配置DeepLink
如何使用DeepLink,推荐使用Expo提供的方式,如果使用Expo直接在app.json
中配置:
{
"expo": {
"scheme": "lucky"
}
}
2
3
4
5
scheme
用于标识当前应用的唯一标识符,就类似与web的domain
,因此这个是唯一的,如果和其他app重复了,那么在app中打开时可能就会出现问题
只需要配置这个现在就可以使用了,重新运行app
那么如何测试DeepLink配置正确呢?
测试DeepLink
- 一种方式就是打包后通过安装app进行测试,这种就接近真实环境,但是比较麻烦
- 另一种就是通过ExpoGo
为了测试的便捷,推荐使用ExpoGo直接测试
uri-scheme
终端脚本测试
npx uri-scheme open exp://192.168.3.23:8081/--/user/detail --ios
› iOS: Opening URI "exp://192.168.3.23:8081/--/user/detail" in simulator
2
默认情况下Expo的Scheme为exp://127.0.0.1:8081
,你可以将其换为自己环境ip,然后拼接/--/路径
,去除掉自己的设置的scheme
如果测试没有问题,模拟器将会自动打开测试的页面
- 通过web
a标签
跳转
<a href="exp://192.168.3.23:8081/--/user/detail">跳转到App用户详情页</a>
来看看效果
总结
React Native路由核心相关的知识就这么多,当然还有一些其他的功能,就不多说了,读者讲这些多加练习其他看看文档就没问题了