你可以不使用 @react-router/dev
,而是通过 Data Mode 将 React Router 的框架特性(例如 loaders、actions、fetchers 等)集成到你自己的 bundler 和服务端抽象中。
启用 route module APIs(例如 loaders、actions 等)的浏览器 runtime API 是 createBrowserRouter
。
它接受一个 route objects 数组,这些对象支持 loaders、actions、error boundaries 等。React Router Vite plugin 会从 routes.ts
创建一个这样的对象,但你也可以手动创建(或通过一个 abstraction)并使用你自己的 bundler。
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,
}),
},
],
},
]);
要在浏览器中渲染 router,请使用 <RouterProvider>
。
import {
createBrowserRouter,
RouterProvider,
} from "react-router";
import { createRoot } from "react-dom/client";
createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
);
Routes 可以通过 lazy
属性进行大部分的定义懒加载。
createBrowserRouter([
{
path: "/show/:showId",
lazy: {
loader: async () =>
(await import("./show.loader.js")).loader,
action: async () =>
(await import("./show.action.js")).action,
Component: async () =>
(await import("./show.component.js")).Component,
},
},
]);
要 server render 一个 custom setup,有一些可用于 rendering 和 data loading 的 server APIs。
本指南仅提供关于其工作原理的一些思路。如需更深入的理解,请参阅自定义框架示例仓库
Routes 在服务端和客户端都是同一种类型的 objects。
export default [
{
path: "/",
Component: Root,
children: [
{
path: "shows/:showId",
Component: Show,
loader: ({ params }) => {
return db.loadShow(params.id);
},
},
],
},
];
使用 createStaticHandler
将你的 routes 转换为一个 request handler
import { createStaticHandler } from "react-router";
import routes from "./some-routes";
let { query, dataRoutes } = createStaticHandler(routes);
React Router 支持 web fetch Requests,所以如果你的服务端不支持,你需要将它使用的任何 objects 适配成一个 web fetch Request
object。
此步骤假设你的服务端接收 Request
objects。
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,
});
}
Hydration data 会嵌入到 window.__staticRouterHydrationData
中,使用它来初始化你的 client side router 并 render 一个 <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>
);