Expo Router 中的导航布局

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


Introduction to Expo Router Layout Files
Introduction to Expo Router Layout Files

What are layout files, how to navigate between screens, and block access using redirects.

app 目录下的每个目录(包括 app 本身)都可以在该目录中以 _layout.tsx 文件的形式定义布局。此文件定义了该目录中所有页面的排列方式。你可以在此处定义堆栈导航器、标签导航器、抽屉导航器或任何其他要用于该目录中页面的布局。布局文件导出一个默认组件,该组件会在你在该目录中导航到的任何页面之前渲染。

¥Each directory within the app directory (including app itself) can define a layout in the form of a _layout.tsx file inside that directory. This file defines how all the pages within that directory are arranged. This is where you would define a stack navigator, tab navigator, drawer navigator, or any other layout that you want to use for the pages in that directory. The layout file exports a default component that is rendered before whatever page you are navigating to within that directory.

让我们看一些常见的布局场景。

¥Let's look at a few common layout scenarios.

根布局

¥Root layout

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

¥Virtually every app will have a _layout.tsx file directly inside the app directory. This is the root layout and represents the entry point for your navigation. In addition to describing the top-level navigator for your app, this file is where you would put initialization code that may have previously gone inside an App.jsx file, such as loading fonts, interacting with the splash screen, or adding context providers.

以下是根布局示例:

¥Here's an example root layout:

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.hideAsync();
    }
  }, [loaded]);

  if (!loaded) {
    return null;
  }

  return <Stack />;
}

以上示例最初显示启动画面,然后在字体加载完成后渲染堆栈导航器,这将使你的应用继续执行初始路由。

¥The above example shows the splash screen initially and then renders a stack navigator once the fonts are loaded, which will cause your app to proceed to the initial route.

堆栈

¥Stacks

你可以在根布局中实现堆栈导航器(如上所示),也可以在目录内的任何其他布局文件中实现。假设你有一个文件结构,其目录内有一个堆栈:

¥You can implement a stack navigator in your root layout, as shown above, or in any other layout file inside a directory. Let's suppose you have a file structure with a stack inside of a directory:

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

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

¥If you want everything inside of the app/products directory to be arranged in a stack relationship, inside the _layout.tsx file, return a Stack component:

app/products/_layout.tsx
import { Stack } from 'expo-router';

export default function StackLayout() {
  return <Stack />;
}

当你导航到 /products 时,它将首先转到默认路由,即 products/index.tsx。如果你导航到 /products/123,则该页面将被推送到堆栈。默认情况下,堆栈将在标题中渲染一个后退按钮,该按钮会将当前页面从堆栈中弹出,并让用户返回到上一页。即使页面不可见,只要它仍然被推送到堆栈中,它就会被渲染。

¥When you navigate to /products, it will first go to the default route, which is products/index.tsx. If you navigate to /products/123, then that page will be pushed onto the stack. By default, the stack will render a back button in the header that will pop the current page off the stack, returning the user to the previous page. Even when a page isn't visible, if it is still pushed onto the stack, it is still being rendered.

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

¥The Stack component implements React Navigation's native stack and can use the same screen options. However, you do not have to define the pages specifically inside the navigator. The files inside the directory will be automatically treated as eligible routes in the stack. However, if you want to define screen options, you can add a Stack.Screen component inside the Stack component. The name prop should match the route name, but you do not need to supply a component prop; Expo Router will map this automatically:

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 的目录,否则,请使用与父目录相同的导航器。

¥While it is possible to nest navigators, be sure to only do so when it is truly needed. In the above example, if you want to push products/accessories/index.tsx onto the stack, it's not necessary to have an additional _layout.tsx in the accessories directory with a Stack navigator. That would define another stack inside the first one. It is fine to add directories that only affect the URL, otherwise, use the same navigator as the parent directory.

选项卡

¥Tabs

与堆栈非常相似,你可以在布局文件中实现一个标签页导航器,并且该目录中的所有路由都将被视为标签页。考虑以下文件结构:

¥Much like a stack, you can implement a tab navigator in your layout file, and all the routes directly inside that directory will be treated as tabs. Consider the following file structure:

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

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

¥In the _layout.tsx file, return a Tabs component:

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: 'Home',
          tabBarIcon: ({ color }) => <MaterialIcons size={28} name="house.fill" color={color} />,
        }}
      />
      <!-- Add more tabs here -->
    </Tabs>
  );
}

这将导致 index.tsx、feed.tsx 和 profile.tsx 文件一起出现在同一个底部选项卡导航器中。此 Tabs 组件使用 React Navigation 原生底部标签页 并支持相同的选项。

¥This will cause the index.tsx, feed.tsx, and profile.tsx files to appear together in the same bottom tabs navigator. This Tabs component uses React Navigation's native bottom tabs and supports the same options.

对于 Tabs,你可能需要在导航器中定义标签页,因为这会影响标签页的显示顺序、标题以及标签页内的图标。索引路由将是默认选定的选项卡。

¥In the case of Tabs, you will likely want to define the tabs in the navigator, as this influences the order in which tabs appear, the title, and the icon inside the tab. The index route will be the default selected tab.

插槽

¥Slot

在某些情况下,你可能需要一个没有导航器的布局。这对于在当前路由周围添加页眉或页脚,或在目录内的任何路由上显示模态框非常有用。在这种情况下,你可以使用 Slot 组件,它作为当前子路由的占位符。

¥In some cases, you may want a layout without a navigator. This is helpful for adding a header or footer around the current route, or for displaying a modal over any route inside a directory. In this case, you can use the Slot component, which serves as a placeholder for the current child route.

考虑以下文件结构:

¥Consider the following file structure:

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

例如,你可能希望用页眉和页脚包裹社交目录内的任何路由,但你希望在页面之间导航时直接替换当前页面,而不是将新页面推送到堆栈上,然后可以通过 "back" 导航操作将其弹出。在 _layout.tsx 文件中,返回一个由页眉和页脚包围的 Slot 组件:

¥For example, you may want to wrap any route inside the social directory with a header and footer, but you want navigating between the pages to simply replace the current page rather than pushing new pages onto a stack, which can then later be popped off with a "back" navigation action. In the _layout.tsx file, return a Slot component surrounded by your header and footer:

app/social/_layout.tsx
import { Slot } from 'expo-router';

export default function Layout() {
  return (
    <>
      <Header />
      <Slot />
      <Footer />
    </>
  );
}

其他布局

¥Other layouts

这些只是一些常见布局的示例,旨在帮助你了解其工作原理。布局功能还有很多其他用途:

¥These are just a few examples of common layouts to give you an idea of how it works. There's much more you can do with layout: