基于文件的路由

编辑

了解基于文件的路由系统 Expo Router,以及如何在您的项目中使用它。


本指南为 Expo Router 及导航模式(堆栈和标签页)提供基本约定和指导。你可以通过使用默认模板创建项目 ,或在现有项目中手动安装 Expo Router 库来跟随本教程。

什么是 Expo Router?

Expo Router 是一个用于 React Native 和 Web 应用的路由框架。它可以帮助你管理应用内各屏幕之间的导航,并在多个平台(Android、iOS 和 Web)上复用相同的组件。它采用基于文件的方法来确定应用内的路由,同时还提供原生导航功能,并构建于 React Navigation 之上。

app 目录

app 是一个特殊的目录。你在此目录中添加的任何文件都会成为原生应用中的一个路由,并且在网页上也会反映出相同的路由 URL。

创建路由

app 目录中,通过添加一个文件或包含 index.tsx 文件的嵌套目录来创建一个路由。

例如,要创建应用程序的初始路由,可以在 app 目录下添加 index.tsx 文件,并使用以下代码:

app/index.tsx
import { View, Text, StyleSheet } from 'react-native'; export default function HomeScreen() { return ( <View style={styles.container}> <Text>Home</Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, });

app/index.tsx 文件将会匹配 / 路由,创建此文件后,app 目录结构如下:

app
index.tsxmatches '/'

文件命名规范

名为 index 的文件会匹配其父目录,并且不会添加路径片段。例如,如果你通过添加 app/settings/index.tsx 扩展 app 的文件结构,它将会匹配 /settings 路由。

app
index.tsxmatches '/'
settings
  index.tsxmatches '/settings'

注意: 路由文件通过导出一个 React 组件作为默认值来定义。该文件必须使用 .js.jsx.ts.tsx 扩展名。

_layout 文件

目录中的布局文件用于定义共享的 UI 元素,例如头部、标签栏等,以便在不同路由之间保持这些元素的持久存在。

每当你创建一个新项目时,app 目录默认会包含一个 根布局 文件(app/_layout)。

app
index.tsxmatches '/'
_layoutRoot layout

根布局

传统上,React Native 项目的结构是由一个根组件(定义为 App.jsindex.js)组成。类似地,app 目录下的第一个布局文件(_layout.tsx)也被视为单一的根组件。

在多个路由之间,Expo Router 中的 Root 布局文件用于在多个路由之间共享 UI,例如注入全局 provider、主题、样式、延迟启动画面渲染直到资源和字体加载完成,或定义应用的根导航结构。

例如,以下代码导出了一个名为 RootLayout 的默认 React 组件:

app/_layout.tsx
export default function RootLayout() { return ( %%placeholder-start%%... %%placeholder-end%% ) }
使用 Expo Router 时,任何在 app/_layout.tsx 中定义的 React provider 都可以被应用中的任意路由访问。为了提升性能并减少渲染次数,建议将 provider 的作用范围缩小到仅需要它们的路由。

堆栈导航器

堆栈导航器是一种在应用中不同路由之间导航的模式。它允许在各个屏幕之间切换,并管理导航历史。其概念上类似于网页浏览器处理导航状态的方式。

例如,如果你想添加一个新的路由 /details,请创建 details.tsx 文件。这将允许应用用户从 / 路由导航到 /details

app/details.tsx
import { View, Text, StyleSheet } from 'react-native'; export default function DetailsScreen() { return ( <View style={styles.container}> <Text>Details</Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, });

创建此路由文件后,当前的文件结构如下:

app
index.tsxmatches '/'
details.tsxmatches '/details'
_layoutRoot layout

要实现两个路由(//details)之间的导航,请更新 Root 布局文件,并添加一个 Stack 组件:

app/_layout.tsx
import { Stack } from 'expo-router'; export default function RootLayout() { return ( <Stack screenOptions={{ headerStyle: { backgroundColor: '#f4511e', }, headerTintColor: '#fff', headerTitleStyle: { fontWeight: 'bold', }, }}> <Stack.Screen name="index" /> <Stack.Screen name="details" /> </Stack> ); }

<Stack.Screen name={routeName} /> 组件在布局文件中允许在堆栈中定义路由。

注意: 上面示例中的 screenOptions 允许为 stack 内的所有路由配置选项。更多信息请参见 静态配置路由选项

在路由之间导航

Expo Router 使用一个名为 Link 的内置组件在应用内不同路由之间跳转。这在概念上类似于网页中的 <a> 标签和 href 属性的用法。

你可以通过从 Expo Router 库中导入该组件,并将要跳转的路由作为 href 属性的值传递给它来使用。例如,要从 / 跳转到 /details,可以在 index.tsx 文件中添加一个 Link 组件:

app/index.tsx
import { Link } from 'expo-router'; import { View, Text, StyleSheet } from 'react-native'; export default function HomeScreen() { return ( <View style={styles.container}> <Text>Home</Text> <Link href="/details">View details</Link> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, });

Link 是如何工作的?

Link 默认会将子元素包裹在一个 <Text> 组件中。你可以自定义为使用其他按钮组件。

使用 Link 组件包裹自定义按钮组件,并使用 asChild 属性,该属性会将所有 props 转发给 Link 组件的第一个子元素。关于 Link 组件的更多属性信息,请参见 在页面之间导航

分组

分组是为了组织相似的路由或应用的某一部分。每个分组都有一个布局文件,并且分组目录需要在括号内命名 (group)

例如,你有 //details 路由,可以将它们分组到 app/(home) 目录下。这样文件结构会变为:

app
_layout.tsxRoot layout
(home)
  index.tsxmatches '/'
  details.tsxmatches '/details'
  _layout.tsxHome layout

你还需要添加 (home)/_layout.tsx,用于为 //details 路由定义 Stack 导航器。

app/(home)/_layout.tsx
import { Stack } from 'expo-router'; export default function HomeLayout() { return ( <Stack screenOptions={{ headerStyle: { backgroundColor: '#f4511e', }, headerTintColor: '#fff', headerTitleStyle: { fontWeight: 'bold', }, }}> <Stack.Screen name="index" /> <Stack.Screen name="details" /> </Stack> ); }

根布局文件也发生了变化,现在包含了 (home) 分组,并进一步将 (home)/index 作为应用的初始路由。

app/_layout.tsx
import { Stack } from 'expo-router'; export default function RootLayout() { return ( <Stack> <Stack.Screen name="(home)" /> </Stack> ); }

注意: 在上面的示例中,屏幕选项被移动到了 (home)/_layout.tsx 文件中。这意味着如果你在 Root 布局中的 Stack navigator 添加任何路由,它将不会使用 Home 布局中路由相同的屏幕选项。

Tab navigator

Tab navigator(标签导航器)是一种常见的模式,用于通过标签栏在应用的不同部分之间导航。Expo Router 提供了一个 Tabs 导航组件。

例如,在当前的文件结构中,你有两个不同的部分:Home(//details 路由)和 Settings(/settings 路由)。通过添加一个特殊目录 (tabs),你可以将现有的 Home 路由文件移到该目录下,并创建一个 settings.tsx

app
_layout.tsxRoot layout
(tabs)
  _layout.tsxTab layout
  (home)
   index.tsxmatches '/'
   details.tsxmatches '/details'
   _layout.tsxHome layout
  settings.tsxmatches '/settings'

(tabs) 目录下的任何文件或文件夹都会成为标签页导航器中的一个路由。要通过标签栏在不同路由之间切换,需要在该目录下创建一个布局文件 (tabs)/_layout,并导出一个 TabLayout 组件:

app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router'; export default function TabLayout() { return ( <Tabs> <Tabs.Screen name="(home)" /> <Tabs.Screen name="settings" /> </Tabs> ); }

注意:TabLayout 中,现有的 (home) 的 Stack 导航器现在已被嵌套。

要使其生效,请在 app/_layout.tsx 文件中将 (tabs) 添加为第一个路由。

app/_layout.tsx
import { Stack } from 'expo-router'; export default function RootLayout() { return ( <Stack> <Stack.Screen name="(tabs)" /> </Stack> ); }

未找到路由

Expo Router 提供了一个特殊文件 +not-found.tsx,用于处理 404 路由。该路由文件会匹配所有未被嵌套路由捕获的路径。

app 目录下创建此文件:

+not-found.tsx
import { Link, Stack } from 'expo-router'; import { View, StyleSheet } from 'react-native'; export default function NotFoundScreen() { return ( <> <Stack.Screen options={{ title: "Oops! This screen doesn't exist." }} /> <View style={styles.container}> <Link href="/">Go to home screen</Link> </View> </> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, });