根目录

根路由

“根”路由 (app/root.tsx) 是 Remix 应用程序中唯一 *必需* 的路由,因为它是你 routes/ 目录中所有路由的父级,并负责渲染根 <html> 文档。

除此之外,它与任何其他路由几乎完全相同,并支持所有标准路由导出

由于根路由管理你的文档,因此它是渲染 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 个目的

  • 避免在根组件、HydrateFallbackErrorBoundary 中重复你的文档/“应用程序外壳”。
  • 避免在根组件/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>
  );
}

另请参阅

文档和示例根据以下许可证授权 MIT