主分支
分支
主分支 (6.23.1)开发分支
版本
6.23.1v4/5.xv3.x
路由
在本页

Route

路由可能是 React Router 应用程序中最重要的部分。它们将 URL 段与组件、数据加载和数据变异相结合。通过路由嵌套,复杂的应用程序布局和数据依赖关系变得简单和声明式。

路由是传递给路由器创建函数的对象

const router = createBrowserRouter([
  {
    // it renders this element
    element: <Team />,

    // when the URL matches this segment
    path: "teams/:teamId",

    // with this data loaded before rendering
    loader: async ({ request, params }) => {
      return fetch(
        `/fake/api/teams/${params.teamId}.json`,
        { signal: request.signal }
      );
    },

    // performing this mutation when data is submitted to it
    action: async ({ request }) => {
      return updateFakeTeam(await request.formData());
    },

    // and renders this element in case something went wrong
    errorElement: <ErrorBoundary />,
  },
]);

您也可以使用 JSX 和 createRoutesFromElements 声明您的路由,传递给元素的 props 与路由对象的属性相同

const router = createBrowserRouter(
  createRoutesFromElements(
    <Route
      element={<Team />}
      path="teams/:teamId"
      loader={async ({ params }) => {
        return fetch(
          `/fake/api/teams/${params.teamId}.json`
        );
      }}
      action={async ({ request }) => {
        return updateFakeTeam(await request.formData());
      }}
      errorElement={<ErrorBoundary />}
    />
  )
);

两种风格都不被反对,行为相同。在本文档的大部分内容中,我们将使用 JSX 风格,因为这是大多数人在 React Router 上下文中习惯使用的风格。

当使用 RouterProvider 时,如果您不想指定 React 元素(即 element={<MyComponent />}),您可以改为指定 Component(即 Component={MyComponent}),React Router 会在内部为您调用 createElement。但是,您应该只在 RouterProvider 应用程序中这样做,因为在 <Routes> 中使用 Component 会降低 React 在渲染之间重用创建元素的能力。

类型声明

interface RouteObject {
  path?: string;
  index?: boolean;
  children?: RouteObject[];
  caseSensitive?: boolean;
  id?: string;
  loader?: LoaderFunction;
  action?: ActionFunction;
  element?: React.ReactNode | null;
  hydrateFallbackElement?: React.ReactNode | null;
  errorElement?: React.ReactNode | null;
  Component?: React.ComponentType | null;
  HydrateFallback?: React.ComponentType | null;
  ErrorBoundary?: React.ComponentType | null;
  handle?: RouteObject["handle"];
  shouldRevalidate?: ShouldRevalidateFunction;
  lazy?: LazyRouteFunction<RouteObject>;
}

path

与 URL 匹配的路径模式,用于确定此路由是否与 URL、链接 href 或表单操作匹配。

动态段

如果路径段以 : 开头,则它将成为一个“动态段”。当路由与 URL 匹配时,动态段将从 URL 中解析出来,并作为 params 提供给其他路由器 API。

<Route
  // this path will match URLs like
  // - /teams/hotspur
  // - /teams/real
  path="/teams/:teamId"
  // the matching param will be available to the loader
  loader={({ params }) => {
    console.log(params.teamId); // "hotspur"
  }}
  // and the action
  action={({ params }) => {}}
  element={<Team />}
/>;

// and the element through `useParams`
function Team() {
  let params = useParams();
  console.log(params.teamId); // "hotspur"
}

您可以在一个路由路径中拥有多个动态段

<Route path="/c/:categoryId/p/:productId" />;
// both will be available
params.categoryId;
params.productId;

动态段不能是“部分”的

  • 🚫 "/teams-:teamId"
  • "/teams/:teamId"
  • 🚫 "/:category--:productId"
  • "/:productSlug"

您仍然可以支持这样的 URL 模式,您只需要进行一些自己的解析

function Product() {
  const { productSlug } = useParams();
  const [category, product] = productSlug.split("--");
  // ...
}

可选段

您可以通过在段的末尾添加 ? 来使路由段可选。

<Route
  // this path will match URLs like
  // - /categories
  // - /en/categories
  // - /fr/categories
  path="/:lang?/categories"
  // the matching param might be available to the loader
  loader={({ params }) => {
    console.log(params["lang"]); // "en"
  }}
  // and the action
  action={({ params }) => {}}
  element={<Categories />}
/>;

// and the element through `useParams`
function Categories() {
  let params = useParams();
  console.log(params.lang);
}

您也可以拥有可选的静态段

<Route path="/project/task?/:taskId" />

通配符

也称为“catchall”和“star”段。如果路由路径模式以 /* 结尾,则它将匹配 / 之后的所有字符,包括其他 / 字符。

<Route
  // this path will match URLs like
  // - /files
  // - /files/one
  // - /files/one/two
  // - /files/one/two/three
  path="/files/*"
  // the matching param will be available to the loader
  loader={({ params }) => {
    console.log(params["*"]); // "one/two"
  }}
  // and the action
  action={({ params }) => {}}
  element={<Team />}
/>;

// and the element through `useParams`
function Team() {
  let params = useParams();
  console.log(params["*"]); // "one/two"
}

您可以解构 *,您只需要为它分配一个新名称。一个常见的名称是 splat

let { org, "*": splat } = params;

布局路由

省略路径会使此路由成为“布局路由”。它参与 UI 嵌套,但不会向 URL 添加任何段。

<Route
  element={
    <div>
      <h1>Layout</h1>
      <Outlet />
    </div>
  }
>
  <Route path="/" element={<h2>Home</h2>} />
  <Route path="/about" element={<h2>About</h2>} />
</Route>

在此示例中,<h1>Layout</h1> 将与每个子路由的 element prop 一起渲染,通过布局路由的 Outlet

index

确定路由是否为索引路由。索引路由在其父级的 URL(如默认子路由)处渲染到其父级的 Outlet 中。

<Route path="/teams" element={<Teams />}>
  <Route index element={<TeamsIndex />} />
  <Route path=":teamId" element={<Team />} />
</Route>

这些特殊路由一开始可能难以理解,因此我们在这里有一个专门针对它们的指南:索引路由

children

(TODO:需要讨论嵌套,甚至可能是一份单独的文档)

caseSensitive

指示路由是否匹配大小写

<Route caseSensitive path="/wEll-aCtuA11y" />
  • 将匹配 "wEll-aCtuA11y"
  • 将不匹配 "well-actua11y"

loader

路由加载器在路由渲染之前被调用,并通过 useLoaderData 为元素提供数据。

<Route
  path="/teams/:teamId"
  loader={({ params }) => {
    return fetchTeam(params.teamId);
  }}
/>;

function Team() {
  let team = useLoaderData();
  // ...
}

如果您没有使用数据路由器,例如 createBrowserRouter,这将不会做任何事情

有关更多详细信息,请参阅 加载器 文档。

action

当从 Formfetchersubmission 向路由发送提交时,将调用路由操作。

<Route
  path="/teams/:teamId"
  action={({ request }) => {
    const formData = await request.formData();
    return updateTeam(formData);
  }}
/>

如果您没有使用数据路由器,例如 createBrowserRouter,这将不会做任何事情

有关更多详细信息,请参阅 操作 文档。

element/Component

当路由与 URL 匹配时要渲染的 React 元素/组件。

如果您想自己创建 React 元素,请使用 element

<Route path="/for-sale" element={<Properties />} />

否则使用 Component,React Router 会为您创建 React 元素

<Route path="/for-sale" Component={Properties} />

您应该只在通过 RouterProvider 的数据路由中选择 Component API。在 <Routes> 中的 <Route> 上使用此 API 会降低 React 在渲染之间重用创建元素的能力。

errorElement/ErrorBoundary

当路由在渲染时、在 loader 中或在 action 中抛出异常时,此 React 元素/组件将被渲染,而不是正常的 element/Component

如果您想自己创建 React 元素,请使用 errorElement

<Route
  path="/for-sale"
  // if this throws an error while rendering
  element={<Properties />}
  // or this while loading properties
  loader={() => loadProperties()}
  // or this while creating a property
  action={async ({ request }) =>
    createProperty(await request.formData())
  }
  // then this element will render
  errorElement={<ErrorBoundary />}
/>

否则使用 ErrorBoundary,React Router 会为您创建 React 元素

<Route
  path="/for-sale"
  Component={Properties}
  loader={() => loadProperties()}
  action={async ({ request }) =>
    createProperty(await request.formData())
  }
  ErrorBoundary={ErrorBoundary}
/>

如果您没有使用数据路由器,例如 createBrowserRouter,这将不会做任何事情

有关更多详细信息,请参阅 errorElement 文档。

hydrateFallbackElement/HydrateFallback

如果您正在使用 服务器端渲染 并且您正在利用 部分水合,那么您可以指定一个元素/组件,在应用程序的初始水合期间为未水合的路由渲染。

如果您没有使用数据路由器,例如 createBrowserRouter,这将不会做任何事情

这仅适用于更高级的用例,例如 Remix 的 clientLoader 功能。大多数 SSR 应用程序不需要利用这些路由属性。

有关更多详细信息,请参阅 hydrateFallbackElement 文档。

handle

任何特定于应用程序的数据。有关详细信息和示例,请参阅 useMatches 文档。

lazy

为了保持应用程序包的大小并支持路由的代码拆分,每个路由都可以提供一个异步函数,该函数解析路由定义(loaderactionComponent/elementErrorBoundary/errorElement 等)的非路由匹配部分。

每个 lazy 函数通常会返回动态导入的结果。

let routes = createRoutesFromElements(
  <Route path="/" element={<Layout />}>
    <Route path="a" lazy={() => import("./a")} />
    <Route path="b" lazy={() => import("./b")} />
  </Route>
);

然后在您的延迟加载路由模块中,导出您希望为路由定义的属性

export async function loader({ request }) {
  let data = await fetchData(request);
  return json(data);
}

export function Component() {
  let data = useLoaderData();

  return (
    <>
      <h1>You made it!</h1>
      <p>{data}</p>
    </>
  );
}

如果您没有使用数据路由器,例如 createBrowserRouter,这将不会做任何事情

有关更多详细信息,请参阅 延迟加载 文档。