root

根路由

¥Root Route

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

¥The "root" route (app/root.tsx) is the only required route in your Remix application because it is the parent to all routes in your routes/ directory and is in charge of rendering the root <html> document.

除此之外,它与其他路由基本相同,并支持所有标准路由导出:

¥Beyond that, it's mostly just like any other route and supports all the standard route exports:

由于根路由管理你的文档,因此它是渲染 Remix 提供的少量 "document-level" 组件的理想位置。这些组件将在根路由中使用一次,它们包含 Remix 为确保页面正确渲染而计算或构建的所有内容。

¥Because the root route manages your document, it is the proper place to render a handful of "document-level" components Remix provides. These components are to be used once inside your root route, and they include everything Remix figured out or built in order for your page to render properly.

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 Export

由于根路由管理所有路由的文档,它还支持额外的可选 Layout 导出。你可以阅读此 RFC 中的详细信息,但布局路由有两个用途:

¥Because the root route manages the document for all routes, it also supports an additional optional Layout export. You can read the details in this RFC but the layout route serves two purposes:

  • 避免在根组件、HydrateFallbackErrorBoundary 之间重复 document/"应用外壳"。

    ¥Avoid duplicating your document/"app shell" across your root component, HydrateFallback, and ErrorBoundary

  • 避免在根组件/HydrateFallback/ErrorBoundary 之间切换时,React 重新挂载应用外壳元素。如果 React 从 <Links> 组件中删除并重新添加 <link rel="stylesheet"> 标签,则可能导致 FOUC。

    ¥Avoids React from re-mounting your app shell elements when switching between the root component/HydrateFallback/ErrorBoundary which can cause a FOUC if React removes and re-adds <link rel="stylesheet"> tags from your <Links> component.

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 的说明

¥A note on useLoaderDatain the Layout Component

useLoaderData 不允许在 ErrorBoundary 组件中使用,因为它旨在用于快乐路径路由渲染,并且其类型内置了 loader 成功运行并返回内容的假设。这个假设在 ErrorBoundary 中不成立,因为可能是 loader 抛出了边界并触发了边界!要访问 ErrorBoundary 中的加载器数据,可以使用 useRouteLoaderData,因为加载器数据可能为 undefined

¥useLoaderData is not permitted to be used in ErrorBoundary components because it is intended for the happy-path route rendering, and its typings have a built-in assumption that the loader ran successfully and returned something. That assumption doesn't hold in an ErrorBoundary because it could have been the loader that threw and triggered the boundary! To access loader data in ErrorBoundary's, you can use useRouteLoaderData which accounts for the loader data potentially being undefined.

因为你的 Layout 组件在成功和错误流程中都会用到,所以同样的限制也适用。如果你需要根据请求是否成功在 Layout 中分叉逻辑,你可以使用 useRouteLoaderData("root")useRouteError()

¥Because your Layout component is used in both success and error flows, this same restriction holds. If you need to fork logic in your Layout depending on if it was a successful request or not, you can use useRouteLoaderData("root") and 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>
  );
}

另请参阅:

¥See also:

Remix v2.17 中文网 - 粤ICP备13048890号