“根”路由 (app/root.tsx
) 是 React Router 应用程序中唯一必需的路由,因为它是所有路由的父级,并负责渲染根 <html>
文档。
import { Outlet, Scripts } from "react-router";
import "./global-styles.css";
export default function App() {
return (
<html lang="en">
<head>
<link rel="icon" href="/favicon.ico" />
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
);
}
因为根路由管理着您的文档,所以它是渲染 React Router 提供的一些“文档级”组件的正确位置。这些组件将在您的根路由中使用一次,它们包含了 React Router 为使您的页面正确渲染而计算或构建的所有内容。
import {
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
</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>
);
}
如果您未使用 React 19,或者选择不使用 React 的 <link>
、<title>
和 <meta>
组件,而是依赖于 React Router 的 links
和 meta
导出,您需要将以下内容添加到您的根路由中。
import { Links, Meta } from "react-router";
export default function App() {
return (
<html lang="en">
<head>
{/* All `meta` exports on all routes will render here */}
<Meta />
{/* All `link` exports on all routes will render here */}
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
根路由支持所有路由模块导出。
根路由还支持一个额外的可选 Layout
导出。Layout
组件有两个作用:
HydrateFallback
和 ErrorBoundary
之间重复文档的“应用外壳”HydrateFallback
/ErrorBoundary
之间切换时重新挂载您的应用外壳元素,如果 React 从 <Links>
组件中移除并重新添加 <link rel="stylesheet">
标签,这可能导致无样式内容闪烁(FOUC)。Layout
接收一个 children
属性,它是 default
导出(例如 App
)、HydrateFallback
或 ErrorBoundary
。
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
的说明
在 ErrorBoundary
组件中不允许使用 useLoaderData
,因为它旨在用于正常路径的路由渲染,并且其类型定义内置了一个假设,即 loader
成功运行并返回了数据。这个假设在 ErrorBoundary
中不成立,因为可能是 loader
抛出错误并触发了错误边界!为了在 ErrorBoundary
中访问 loader 数据,您可以使用 useRouteLoaderData
,它考虑了 loader 数据可能为 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>
);
}