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 />,
},
]);
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
声明路由,元素的属性与路由对象的属性相同:
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 />}
/>
)
);
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 的上下文中,大多数人都习惯使用这种风格。
NOTE
在使用
RouterProvider
时,如果您不想指定 React 元素(即element={<MyComponent />}
),可以指定Component
代替(即Component={MyComponent}
),React 路由器会在内部为您调用createElement
。不过,您只能在RouterProvider
应用程序中这样做,因为在<Routes>
内部使用Component
会降低 React 在不同渲染中重复使用所创建元素的能力。
类型声明
interface RouteObject {
path?: string;
index?: boolean;
children?: React.ReactNode;
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>;
}
interface RouteObject {
path?: string;
index?: boolean;
children?: React.ReactNode;
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
// 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;
<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("--");
// ...
}
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
// 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" />
<Route path="/project/task?/:taskId" />
Splats
也称为“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"
}
<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;
let { org, "*": splat } = params;
布局路由
省略路径会使此路由成为“布局路由”。它参与用户界面嵌套,但不会在 URL 中添加任何分段。
<Route
element={
<div>
<h1>Layout</h1>
<Outlet />
</div>
}
>
<Route path="/" element={<h2>Home</h2>} />
<Route path="/about" element={<h2>About</h2>} />
</Route>
<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>
将通过布局路由的 Outlet 与每个子路由的 element
prop 一起呈现。
index
确定路由是否为索引路由。索引路由通过父路由的 URL 呈现到父路由 Outlet 中(就像默认的子路由)。
<Route path="/teams" element={<Teams />}>
<Route index element={<TeamsIndex />} />
<Route path=":teamId" element={<Team />} />
</Route>
<Route path="/teams" element={<Teams />}>
<Route index element={<TeamsIndex />} />
<Route path=":teamId" element={<Team />} />
</Route>
这些特殊路线一开始可能会让人一头雾水,因此我们在这里专门为它们编写了一份指南:索引路由。
children
IMPORTANT
(TODO:需要讨论嵌套问题,甚至可以单独编写一份文档)
caseSensitive
指示路由是否匹配大小写:
<Route caseSensitive path="/wEll-aCtuA11y" />
<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();
// ...
}
<Route
path="/teams/:teamId"
loader={({ params }) => {
return fetchTeam(params.teamId);
}}
/>;
function Team() {
let team = useLoaderData();
// ...
}
IMPORTANT
如果您没有使用数据路由器(如
createBrowserRouter
),这将毫无用处
详情请查看 loader 文档。
action
当从Form、fetcher或submission 向路由发送提交信息时,路由操作将被调用。
<Route
path="/teams/:teamId"
action={({ request }) => {
const formData = await request.formData();
return updateTeam(formData);
}}
/>
<Route
path="/teams/:teamId"
action={({ request }) => {
const formData = await request.formData();
return updateTeam(formData);
}}
/>
如果您没有使用数据路由器(如
createBrowserRouter
),这将毫无用处
更多详情,请参阅 action 文档。
element
/Component
当路由与 URL 匹配时要渲染的 React 元素/组件。
如果要创建 React 元素,请使用 element
:
<Route path="/for-sale" element={<Properties />} />
<Route path="/for-sale" element={<Properties />} />
否则,请使用 Component
,React Router 会为您创建 React 元素:
<Route path="/for-sale" Component={Properties} />
<Route path="/for-sale" Component={Properties} />
IMPORTANT
您只能通过
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 />}
/>
<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}
/>
<Route
path="/for-sale"
Component={Properties}
loader={() => loadProperties()}
action={async ({ request }) =>
createProperty(await request.formData())
}
ErrorBoundary={ErrorBoundary}
/>
IMPORTANT
如果您没有使用数据路由(如
createBrowserRouter
),这将毫无用处
更多详情,请参阅 errorElement 文档。
hydrateFallbackElement
/HydrateFallback
如果您使用服务器端渲染并利用部分水合,那么您可以指定一个元素/组件,以便在应用程序初始水合期间为未水合路由进行渲染。
IMPORTANT
如果您没有使用数据路由(如
createBrowserRouter
),这将毫无用处
IMPORTANT 这仅适用于更高级的使用案例,如 Remix 的
clientLoader
功能。大多数 SSR 应用程序都不需要利用这些路由属性。
详情请查看 hydrateFallbackElement 文档。
handle
任何特定于应用程序的数据。有关详情和示例,请参阅 useMatches 文档。
lazy
为了保持应用程序捆绑包的小巧并支持路由的代码拆分,每个路由都可以提供一个异步函数,用于解析路由定义中与路由不匹配的部分( loader
, action
, Component
/ element
, ErrorBoundary
/ errorElement
等)。
每个 lazy
函数通常都会返回动态导入的结果。
let routes = createRoutesFromElements(
<Route path="/" element={<Layout />}>
<Route path="a" lazy={() => import("./a")} />
<Route path="b" lazy={() => import("./b")} />
</Route>
);
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>
</>
);
}
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>
</>
);
}
IMPORTANT
如果您没有使用数据路由器(如
createBrowserRouter
),这将毫无用处
更多详情,请参阅 lazy 文档。