自訂框架
本頁面

自訂框架

除了使用 @react-router/dev,您可以將 React Router 的框架功能 (例如 loaders、actions、fetchers 等) 整合到您自己的捆綁器和伺服器抽象中。

用戶端渲染

1. 建立路由器

啟用路由模組 API (loaders、actions 等) 的瀏覽器運行時 API 是 createBrowserRouter

它接受一個路由物件陣列,支援 loaders、actions、錯誤邊界等等。React Router Vite 插件會從 routes.ts 建立其中一個,但您可以手動 (或使用抽象) 建立一個,並使用您自己的捆綁器。

import { createBrowserRouter } from "react-router";

let router = createBrowserRouter([
  {
    path: "/",
    Component: Root,
    children: [
      {
        path: "shows/:showId",
        Component: Show,
        loader: ({ request, params }) =>
          fetch(`/api/show/${params.id}.json`, {
            signal: request.signal,
          }),
      },
    ],
  },
]);

2. 渲染路由器

若要在瀏覽器中渲染路由器,請使用 <RouterProvider>

import {
  createBrowserRouter,
  RouterProvider,
} from "react-router";
import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")).render(
  <RouterProvider router={router} />
);

3. 延遲載入

路由可以使用 lazy 屬性延遲載入它們的大部分定義。

createBrowserRouter([
  {
    path: "/show/:showId",
    lazy: () => {
      let [loader, action, Component] = await Promise.all([
        import("./show.action.js"),
        import("./show.loader.js"),
        import("./show.component.js"),
      ]);
      return { loader, action, Component };
    },
  },
]);

伺服器端渲染

若要伺服器端渲染自訂設定,有一些伺服器 API 可用於渲染和資料載入。

本指南僅提供一些關於其運作方式的想法。為了更深入的理解,請參閱 自訂框架範例倉庫

1. 定義您的路由

路由在伺服器上與用戶端上是相同的物件類型。

export default [
  {
    path: "/",
    Component: Root,
    children: [
      {
        path: "shows/:showId",
        Component: Show,
        loader: ({ params }) => {
          return db.loadShow(params.id);
        },
      },
    ],
  },
];

2. 建立靜態處理程序

使用 createStaticHandler 將您的路由轉換為請求處理程序

import { createStaticHandler } from "react-router";
import routes from "./some-routes";

let { query, dataRoutes } = createStaticHandler(routes);

3. 取得路由上下文並渲染

React Router 與 web fetch Requests 協同運作,因此如果您的伺服器不使用,您需要將其使用的任何物件改編為 web fetch Request 物件。

此步驟假設您的伺服器接收 Request 物件。

import { renderToString } from "react-dom/server";
import {
  createStaticHandler,
  createStaticRouter,
  StaticRouterProvider,
} from "react-router";

import routes from "./some-routes.js";

let { query, dataRoutes } = createStaticHandler(routes);

export async function handler(request: Request) {
  // 1. run actions/loaders to get the routing context with `query`
  let context = await query(request);

  // If `query` returns a Response, send it raw (a route probably a redirected)
  if (context instanceof Response) {
    return context;
  }

  // 2. Create a static router for SSR
  let router = createStaticRouter(dataRoutes, context);

  // 3. Render everything with StaticRouterProvider
  let html = renderToString(
    <StaticRouterProvider
      router={router}
      context={context}
    />
  );

  // Setup headers from action and loaders from deepest match
  let leaf = context.matches[context.matches.length - 1];
  let actionHeaders = context.actionHeaders[leaf.route.id];
  let loaderHeaders = context.loaderHeaders[leaf.route.id];
  let headers = new Headers(actionHeaders);
  if (loaderHeaders) {
    for (let [key, value] of loaderHeaders.entries()) {
      headers.append(key, value);
    }
  }

  headers.set("Content-Type", "text/html; charset=utf-8");

  // 4. send a response
  return new Response(`<!DOCTYPE html>${html}`, {
    status: context.statusCode,
    headers,
  });
}

4. 在瀏覽器中 Hydrate

Hydration 資料嵌入到 window.__staticRouterHydrationData,使用它來初始化您的用戶端路由器並渲染 <RouterProvider>

import { StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { RouterProvider } from "react-router/dom";
import routes from "./app/routes.js";
import { createBrowserRouter } from "react-router";

let router = createBrowserRouter(routes, {
  hydrationData: window.__staticRouterHydrationData,
});

hydrateRoot(
  document,
  <StrictMode>
    <RouterProvider router={router} />
  </StrictMode>
);
文件和範例 CC 4.0