This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

Expo Router 中的导航布局

编辑页面

了解如何通过使用目录和布局文件来构建页面之间的不同关系。


Expo Router 布局文件介绍
Expo Router 布局文件介绍

什么是布局文件、如何在屏幕之间导航,以及如何使用重定向阻止访问。

src/app 目录中的每个目录(包括 src/app 本身)都可以通过该目录内的 _layout.tsx 文件定义一个布局。这个文件定义了该目录下所有页面的排列方式。你可以在这里定义栈导航器、标签导航器、抽屉导航器,或任何你希望用于该目录页面的其他布局。布局文件会导出一个默认组件,它会在你导航到该目录中的任意页面之前先被渲染。

让我们来看几个常见的布局场景。

根布局

几乎每个应用都会在 src/app 目录中直接放置一个 _layout.tsx 文件。这就是根布局,代表你导航的入口点。除了描述应用的顶层导航器之外,这个文件也是你放置之前可能写在 App.jsx 文件中的初始化代码的地方,例如加载字体、与启动画面交互,或添加上下文提供器。

下面是一个根布局示例:

src/app/_layout.tsx
import { useFonts } from 'expo-font'; import { Stack } from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; import { useEffect } from 'react'; SplashScreen.preventAutoHideAsync(); export default function RootLayout() { const [loaded] = useFonts({ SpaceMono: require('@/assets/fonts/SpaceMono-Regular.ttf'), }); useEffect(() => { if (loaded) { SplashScreen.hide(); } }, [loaded]); if (!loaded) { return null; } return <Stack />; }

上面的示例会先显示启动画面,然后在字体加载完成后渲染一个栈导航器,这将使你的应用继续进入初始路由。

你可以像上面那样在根布局中实现栈导航器,也可以在目录中的任何其他布局文件中实现。假设你有如下所示、在某个目录内包含栈的文件结构:

src
app
  products
   _layout.tsx
   index.tsx
   [productId].tsx
   accessories
    index.tsx

如果你希望 src/app/products 目录中的所有内容都以栈关系排列,那么在 _layout.tsx 文件中返回一个 Stack 组件:

src/app/products/_layout.tsx
import { Stack } from 'expo-router'; export default function StackLayout() { return <Stack />; }

当你导航到 /products 时,它会先进入默认路由,也就是 products/index.tsx。如果你导航到 /products/123,那么该页面会被压入栈中。默认情况下,栈会在标题栏中渲染一个返回按钮,用于将当前页面从栈中弹出,从而返回到上一页。即使某个页面不可见,只要它仍然被压入栈中,它就仍然在被渲染。

Stack 组件实现了 React Navigation 的原生栈,并且可以使用相同的屏幕选项。不过,你不必在导航器中显式定义页面。目录中的文件会自动被视为栈中的可用路由。但是,如果你想定义屏幕选项,可以在 Stack 组件内部添加一个 Stack.Screen 组件。name 属性应与路由名称匹配,但你不需要提供 component 属性;Expo Router 会自动映射:

src/app/products/_layout.tsx
import { Stack } from 'expo-router'; export default function StackLayout() { return ( <Stack> <Stack.Screen name="[productId]" options={{ headerShown: false }} /> </Stack> ); }

虽然可以嵌套导航器,但只有在真正需要时才应这样做。在上面的示例中,如果你想将 products/accessories/index.tsx 压入栈中,那么就没有必要在 accessories 目录下额外创建一个带有 Stack 导航器的 _layout.tsx。那样会在第一个栈中再定义另一个栈。添加仅影响 URL 的目录是可以的,否则就使用与父目录相同的导航器。

标签页

Expo Router 提供了多种实现标签导航的方式,具体取决于你的需求。

JavaScript 标签页

你可以在布局文件中使用 Tabs 组件实现基于 JavaScript 的标签导航器。该目录下直接包含的所有路由都会被视为标签页。请看下面的文件结构:

src
app
  (tabs)
   _layout.tsx
   index.tsx
   feed.tsx
   profile.tsx

_layout.tsx 文件中,返回一个 Tabs 组件:

src/app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router'; import MaterialIcons from '@expo/vector-icons/MaterialIcons'; export default function TabLayout() { return ( <Tabs> <Tabs.Screen name="index" options={{ title: '首页', tabBarIcon: ({ color }) => <MaterialIcons size={28} name="house.fill" color={color} />, }} /> <Tabs.Screen name="feed" options={{ title: '动态' }} /> <Tabs.Screen name="profile" options={{ title: '个人资料' }} /> </Tabs> ); }

这会使 index.tsxfeed.tsxprofile.tsx 文件一起出现在同一个底部标签导航器中。这个 Tabs 组件使用了 React Navigation 的原生底部标签,并支持相同的选项。

对于 Tabs,你通常会希望在导航器中定义这些标签,因为这会影响标签的显示顺序、标题以及标签内的图标。index 路由将是默认选中的标签页。

原生标签页

在 Android 和 iOS 上,你可以使用 原生标签页 来渲染平台内置的标签栏。原生标签页提供了符合预期的平台行为,例如点击时滚动到顶部、原生动画,以及原生的外观和体验。

与 JavaScript 标签页一样,原生标签页也可以在路由组目录中的布局文件里使用:

src
app
  (tabs)
   _layout.tsx
   index.tsx
   feed.tsx
   profile.tsx
src/app/(tabs)/_layout.tsx
import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { return ( <NativeTabs> <NativeTabs.Trigger name="index"> <NativeTabs.Trigger.Label>首页</NativeTabs.Trigger.Label> <NativeTabs.Trigger.Icon src={require('@/assets/images/tabIcons/home.png')} /> </NativeTabs.Trigger> <NativeTabs.Trigger name="feed"> <NativeTabs.Trigger.Label>动态</NativeTabs.Trigger.Label> <NativeTabs.Trigger.Icon src={require('@/assets/images/tabIcons/feed.png')} /> </NativeTabs.Trigger> <NativeTabs.Trigger name="profile"> <NativeTabs.Trigger.Label>个人资料</NativeTabs.Trigger.Label> <NativeTabs.Trigger.Icon src={require('@/assets/images/tabIcons/profile.png')} /> </NativeTabs.Trigger> </NativeTabs> ); }

平台特定标签页

由于原生标签页仅适用于 Android 和 iOS,一个常见模式是使用 平台特定的文件扩展名 为原生端和 web 提供不同的标签实现。根布局渲染一个标签组件,而 Expo 的模块解析会根据平台自动选择正确的文件。

src
app
  _layout.tsx
  index.tsx
  explore.tsx
components
  app-tabs.native.tsx原生标签页(Android 和 iOS)
  app-tabs.tsx自定义标签页(web)

根布局会导入并渲染 AppTabs 组件。app-tabs.native.tsx 用于 Android 和 iOS,app-tabs.tsx 用于 web:

src/app/_layout.tsx
import AppTabs from '@/components/app-tabs'; export default function RootLayout() { return <AppTabs />; }

在 Android 和 iOS 上,app-tabs.native.tsx 使用 原生标签页

src/components/app-tabs.native.tsx
import { NativeTabs } from 'expo-router/native-tabs'; export default function AppTabs() { return ( <NativeTabs> <NativeTabs.Trigger name="index"> <NativeTabs.Trigger.Label>首页</NativeTabs.Trigger.Label> <NativeTabs.Trigger.Icon src={require('@/assets/images/tabIcons/home.png')} /> </NativeTabs.Trigger> <NativeTabs.Trigger name="explore"> <NativeTabs.Trigger.Label>探索</NativeTabs.Trigger.Label> <NativeTabs.Trigger.Icon src={require('@/assets/images/tabIcons/explore.png')} /> </NativeTabs.Trigger> </NativeTabs> ); }

在 web 上,app-tabs.tsx 使用来自 expo-router/ui自定义标签页,它们是不带样式且灵活的组件:

src/components/app-tabs.tsx
import { Tabs, TabList, TabTrigger, TabSlot } from 'expo-router/ui'; export default function AppTabs() { return ( <Tabs> <TabSlot /> <TabList> <TabTrigger name="index" href="/"> 首页 </TabTrigger> <TabTrigger name="explore" href="/explore"> 探索 </TabTrigger> </TabList> </Tabs> ); }

插槽

在某些情况下,你可能希望使用不带导航器的布局。这对于在当前路由外包裹一个头部或底部,或者在某个目录内的任意路由上方显示一个模态框都很有帮助。在这种情况下,你可以使用 Slot 组件,它充当当前子路由的占位符。

请看下面的文件结构:

src
app
  social
   _layout.tsx
   index.tsx
   feed.tsx
   profile.tsx

例如,你可能希望用头部和底部将 social 目录中的任意路由包裹起来,但你又希望页面之间的导航只是简单地替换当前页面,而不是将新页面压入栈中,然后再通过“返回”导航操作将其弹出。在 _layout.tsx 文件中,返回一个由头部和底部包裹的 Slot 组件:

src/app/social/_layout.tsx
import { Slot } from 'expo-router'; export default function Layout() { return ( <> <Header /> <Slot /> <Footer /> </> ); }

其他布局

这些只是一些常见布局的示例,帮助你了解其工作方式。你还可以通过布局实现更多功能: