“根”路由 (app/root.tsx
) 是 Remix 应用程序中唯一 *必需* 的路由,因为它是你 routes/
目录中所有路由的父级,并负责渲染根 <html>
文档。
除此之外,它与任何其他路由几乎完全相同,并支持所有标准路由导出
headers
meta
links
loader
clientLoader
action
clientAction
default
ErrorBoundary
HydrateFallback
handle
shouldRevalidate
由于根路由管理你的文档,因此它是渲染 Remix 提供的少量“文档级”组件的合适位置。这些组件在你的根路由中仅使用一次,并且包含 Remix 为正确渲染页面而计算或构建的所有内容。
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import globalStylesheetUrl from "./global-styles.css";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: globalStylesheetUrl }];
};
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 />
{/* Sets up automatic reload when you change code */}
{/* and only does anything during development */}
{/* 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. */}
<LiveReload />
</body>
</html>
);
}
由于根路由管理所有路由的文档,因此它也支持额外的可选 Layout
导出。你可以在此 RFC 中阅读详细信息,但布局路由服务于 2 个目的
HydrateFallback
和 ErrorBoundary
中重复你的文档/“应用程序外壳”。HydrateFallback
/ErrorBoundary
之间切换时 React 重新挂载应用程序外壳元素,如果 React 从你的 <Links>
组件中删除和重新添加 <link rel="stylesheet">
标签,这可能会导致 FOUC。import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
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 />
<LiveReload />
</body>
</html>
);
}
export default function App() {
return <Outlet />;
}
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<>
<h1>
{error.status} {error.statusText}
</h1>
<p>{error.data}</p>
</>
);
}
return (
<>
<h1>Error!</h1>
<p>{error?.message ?? "Unknown error"}</p>
</>
);
}
关于 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>
);
}
另请参阅