React Router 在您的项目中查找一些特殊文件。并非所有这些文件都是必需的
此文件是可选的
配置文件用于配置应用程序的某些方面,例如您是否正在使用服务器端渲染、某些目录的位置等等。
import type { Config } from "@react-router/dev/config";
export default {
// Config options...
} satisfies Config;
有关更多信息,请参阅 react-router config API 的详细信息。
此文件是必需的
“根”路由 (app/root.tsx
) 是您的 React Router 应用程序中唯一必需的路由,因为它是 routes/
目录中所有路由的父级,并且负责渲染根 <html>
文档。
由于根路由管理您的文档,因此它是渲染 React Router 提供的一些“文档级别”组件的适当位置。这些组件应在您的根路由内使用一次,它们包含 React Router 为了使您的页面正确渲染而计算或构建的所有内容。
import type { LinksFunction } from "react-router";
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";
import "./global-styles.css";
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
{/* All `meta` exports on all routes will render here */}
<Meta />
{/* All `link` exports on all routes will render here */}
<Links />
</head>
<body>
{/* Child routes render here */}
<Outlet />
{/* Manages scroll position for client-side transitions */}
{/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */}
<ScrollRestoration />
{/* Script tags go here */}
{/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */}
<Scripts />
</body>
</html>
);
}
根路由支持所有路由模块导出。
根路由还支持一个额外的可选 Layout
导出。Layout
组件有两个用途
HydrateFallback
和 ErrorBoundary
中重复文档的“应用程序外壳”HydrateFallback
/ErrorBoundary
之间切换时重新挂载应用程序外壳元素,如果 React 从您的 <Links>
组件中删除并重新添加 <link rel="stylesheet">
标签,这可能会导致 FOUC。export function Layout({ children }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<Meta />
<Links />
</head>
<body>
{/* children will be the root Component, ErrorBoundary, or HydrateFallback */}
{children}
<Scripts />
<ScrollRestoration />
</body>
</html>
);
}
export default function App() {
return <Outlet />;
}
export function ErrorBoundary() {}
关于 Layout
组件中 useLoaderData
的注意事项
useLoaderData
不允许在 ErrorBoundary
组件中使用,因为它旨在用于正常路径路由渲染,并且其类型定义内置了一个假设,即 loader
成功运行并返回了一些内容。该假设在 ErrorBoundary
中不成立,因为可能是 loader
抛出并触发了边界!为了在 ErrorBoundary
中访问加载器数据,您可以使用 useRouteLoaderData
,它考虑了加载器数据可能为 undefined
的情况。
由于您的 Layout
组件在成功和错误流程中都使用,因此也存在相同的限制。如果您需要在 Layout
中根据请求是否成功来派生逻辑,则可以使用 useRouteLoaderData("root")
和 useRouteError()
。
<Layout>
组件用于渲染 ErrorBoundary
,因此您应该非常谨慎,以确保您可以渲染 ErrorBoundary
而不会遇到任何渲染错误。如果您的 Layout
在尝试渲染边界时抛出另一个错误,则无法使用它,并且您的 UI 将回退到非常小的内置默认 ErrorBoundary
。
export function Layout({
children,
}: {
children: React.ReactNode;
}) {
const data = useRouteLoaderData("root");
const error = useRouteError();
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<Meta />
<Links />
<style
dangerouslySetInnerHTML={{
__html: `
:root {
--themeVar: ${
data?.themeVar || defaultThemeVar
}
}
`,
}}
/>
</head>
<body>
{data ? (
<Analytics token={data.analyticsToken} />
) : null}
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
此文件是必需的
routes.ts
文件用于配置哪些 URL 模式与哪些路由模块匹配。
import {
type RouteConfig,
route,
} from "@react-router/dev/routes";
export default [
route("some/path", "./some/file.tsx"),
// pattern ^ ^ module file
] satisfies RouteConfig;
有关更多信息,请参阅路由指南。
此文件是可选的
默认情况下,React Router 将为您处理客户端应用程序的 hydration。您可以使用以下命令显示默认的客户端入口文件
react-router reveal
此文件是浏览器的入口点,负责对您的服务器入口模块中服务器生成的标记进行 hydration,但是您也可以在此处初始化任何其他客户端代码。
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<HydratedRouter />
</StrictMode>
);
});
这是在浏览器中运行的第一段代码。您可以初始化客户端库、添加仅客户端提供程序等。
此文件是可选的
默认情况下,React Router 将为您处理生成 HTTP 响应。您可以使用以下命令显示默认的服务器入口文件
react-router reveal
此模块的 default
导出是一个函数,可让您创建响应,包括 HTTP 状态、标头和 HTML,从而使您可以完全控制标记的生成方式和发送到客户端的方式。
此模块应使用带有当前请求的 context
和 url
的 <ServerRouter>
元素渲染当前页面的标记。一旦 JavaScript 在浏览器中使用客户端入口模块加载,此标记将(可选地)重新 hydration。
streamTimeout
如果您正在流式传输响应,则可以导出一个可选的 streamTimeout
值(以毫秒为单位),该值将控制服务器等待流式传输的 Promise 解决的时间,然后再拒绝未完成的 Promise 并关闭流。
建议将此值与中止 React 渲染器的超时时间分离。您应始终将 React 渲染超时设置为更高的值,以便它有时间从您的 streamTimeout
流式传输底层的拒绝。
// Reject all pending promises from handler functions after 10 seconds
export const streamTimeout = 10000;
export default function handleRequest(...) {
return new Promise((resolve, reject) => {
// ...
const { pipe, abort } = renderToPipeableStream(
<ServerRouter context={routerContext} url={request.url} />,
{ /* ... */ }
);
// Abort the streaming render pass after 11 seconds to allow the rejected
// boundaries to be flushed
setTimeout(abort, streamTimeout + 1000);
});
}
handleDataRequest
您可以导出一个可选的 handleDataRequest
函数,该函数将允许您修改数据请求的响应。这些是不渲染 HTML 的请求,而是在客户端 hydration 发生后将加载器和操作数据返回给浏览器。
export function handleDataRequest(
response: Response,
{
request,
params,
context,
}: LoaderFunctionArgs | ActionFunctionArgs
) {
response.headers.set("X-Custom-Header", "value");
return response;
}
handleError
默认情况下,React Router 会将遇到的服务器端错误记录到控制台。如果您希望对日志记录进行更多控制,或者还希望将这些错误报告给外部服务,则可以导出一个可选的 handleError
函数,该函数将为您提供控制权(并将禁用内置的错误日志记录)。
export function handleError(
error: unknown,
{
request,
params,
context,
}: LoaderFunctionArgs | ActionFunctionArgs
) {
if (!request.signal.aborted) {
sendErrorToErrorReportingService(error);
console.error(formatErrorForJsonLogging(error));
}
}
请注意,通常要避免在请求中止时进行日志记录,因为 React Router 的取消和竞态条件处理可能会导致大量请求被中止。
当您通过renderToPipeableStream
或renderToReadableStream
流式传输 HTML 响应时,您自己的 handleError
实现将仅处理初始外壳渲染期间遇到的错误。如果您在后续流式渲染期间遇到渲染错误,则需要手动处理这些错误,因为 React Router 服务器此时已发送响应。
对于 renderToPipeableStream
,您可以在 onError
回调函数中处理这些错误。您需要在 onShellReady
中切换一个布尔值,以便您知道该错误是外壳渲染错误(可以忽略)还是异步错误
有关示例,请参阅 Node 的默认entry.server.tsx
。
抛出的响应
请注意,这不处理从您的 loader
/action
函数抛出的 Response
实例。此处理程序的目的是查找代码中导致意外抛出错误的错误。如果您正在检测一种情况并在您的 loader
/action
中抛出 401/404/etc. Response
,那么这是您的代码处理的预期流程。如果您还希望记录这些错误或将其发送到外部服务,则应在抛出响应时执行此操作。
.server
模块虽然不是绝对必要的,但 .server
模块是显式标记整个模块为仅服务器端的好方法。如果 .server
文件或 .server
目录中的任何代码意外地最终出现在客户端模块图中,则构建将失败。
app
├── .server 👈 marks all files in this directory as server-only
│ ├── auth.ts
│ └── db.ts
├── cms.server.ts 👈 marks this file as server-only
├── root.tsx
└── routes.ts
.server
模块必须在您的应用程序目录中。
有关更多信息,请参阅侧边栏中的路由模块部分。
.client
模块虽然不常见,但您可能有一个文件或依赖项在浏览器中使用模块副作用。您可以在文件名上使用 *.client.ts
或将文件嵌套在 .client
目录中,以强制它们退出服务器捆绑包。
// this would break the server
export const supportsVibrationAPI =
"vibrate" in window.navigator;
请注意,从此模块导出的值在服务器上都将是 undefined
,因此使用它们的唯一位置是在useEffect
和用户事件(如单击处理程序)中。
import { supportsVibrationAPI } from "./feature-check.client.ts";
console.log(supportsVibrationAPI);
// server: undefined
// client: true | false