从 RouterProvider 采用框架
本页内容

从 RouterProvider 采用框架

如果你没有使用 <RouterProvider>,请查看 从组件路由采用框架

React Router Vite 插件为 React Router 添加了框架特性。本指南将帮助你在应用中采用此插件。如果遇到任何问题,请在 TwitterDiscord 上寻求帮助。

特性

Vite 插件添加了

  • 路由加载器、操作和自动数据重新验证
  • 类型安全的路由模块
  • 自动路由代码拆分
  • 跨导航的自动滚动恢复
  • 可选的静态预渲染
  • 可选的服务端渲染

初始设置需要最多的工作。但是,一旦完成,你可以逐步采用新功能。

前提条件

要使用 Vite 插件,你的项目需要

  • Node.js 20+ (如果使用 Node 作为你的运行时)
  • Vite 5+

1. 将路由定义移至路由模块

React Router Vite 插件渲染它自己的 RouterProvider,因此你不能在其内部渲染现有的 RouterProvider。相反,你需要格式化所有路由定义以匹配 路由模块 API

此步骤将花费最长时间,但是无论是否采用 React Router Vite 插件,这样做都有几个好处

  • 路由模块将被懒加载,从而减小应用的初始包大小
  • 路由定义将是统一的,从而简化应用的架构
  • 迁移到路由模块是渐进式的,你可以一次迁移一个路由

👉 将你的路由定义移至路由模块

按照 路由模块 API,将路由定义的每个部分导出为单独的命名导出。

export async function clientLoader() {
  return {
    title: "About",
  };
}

export default function About() {
  let data = useLoaderData();
  return <div>{data.title}</div>;
}

// clientAction, ErrorBoundary, etc.

👉 创建转换函数

创建一个辅助函数,将路由模块定义转换为数据路由器期望的格式

function convert(m: any) {
  let {
    clientLoader,
    clientAction,
    default: Component,
    ...rest
  } = m;
  return {
    ...rest,
    loader: clientLoader,
    action: clientAction,
    Component,
  };
}

👉 懒加载并转换你的路由模块

不要直接导入路由模块,而是懒加载并将它们转换为数据路由器期望的格式。

你的路由定义不仅现在符合路由模块 API,而且你还获得了代码拆分路由的好处。

let router = createBrowserRouter([
  // ... other routes
  {
    path: "about",
-   loader: aboutLoader,
-   Component: About,
+   lazy: () => import("./routes/about").then(convert),
  },
  // ... other routes
]);

为应用中的每个路由重复此过程。

2. 安装 Vite 插件

一旦所有路由定义都转换为路由模块,你就可以采用 React Router Vite 插件。

👉 安装 React Router Vite 插件

npm install -D @react-router/dev

👉 安装运行时适配器

我们将假设你正在使用 Node 作为你的运行时。

npm install @react-router/node

👉 将 React 插件替换为 React Router

-import react from '@vitejs/plugin-react'
+import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";


export default defineConfig({
  plugins: [
-    react()
+    reactRouter()
  ],
});

3. 添加 React Router 配置

👉 创建 react-router.config.ts 文件

将以下内容添加到项目的根目录。在此配置中,你可以告诉 React Router 关于你的项目的信息,例如在哪里找到应用目录以及暂时不使用 SSR(服务端渲染)。

touch react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
  appDirectory: "src",
  ssr: false,
} satisfies Config;

4. 添加 Root 入口点

在典型的 Vite 应用中,index.html 文件是打包的入口点。React Router Vite 插件将入口点移动到 root.tsx 文件,这样你可以使用 React 来渲染应用的 shell 而不是静态 HTML,并且如果需要,最终可以升级到服务端渲染。

👉 将你现有的 index.html 移动到 root.tsx

例如,如果你当前的 index.html 看起来像这样

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0"
    />
    <title>My App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

你应该将该标记移动到 src/root.tsx 并删除 index.html

touch src/root.tsx
import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
} from "react-router";

export function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <head>
        <meta charSet="UTF-8" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1.0"
        />
        <title>My App</title>
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

export default function Root() {
  return <Outlet />;
}

👉 将 RouterProvider 之上的所有内容移动到 root.tsx

任何全局样式、上下文提供程序等都应移动到 root.tsx,以便它们可以在所有路由之间共享。

例如,如果你的 App.tsx 看起来像这样

import "./index.css";

export default function App() {
  return (
    <OtherProviders>
      <AppLayout>
        <RouterProvider router={router} />
      </AppLayout>
    </OtherProviders>
  );
}

你应该将 RouterProvider 之上的所有内容移动到 root.tsx

+import "./index.css";

// ... other imports and Layout

export default function Root() {
  return (
+   <OtherProviders>
+     <AppLayout>
        <Outlet />
+     </AppLayout>
+   </OtherProviders>
  );
}

5. 添加客户端入口模块(可选)

在典型的 Vite 应用中,index.html 文件指向 src/main.tsx 作为客户端入口点。React Router 使用名为 src/entry.client.tsx 的文件代替。

如果 entry.client.tsx 不存在,React Router Vite 插件将使用默认的、隐藏的文件。

👉 将 src/entry.client.tsx 作为你的入口点

如果你的当前 src/main.tsx 看起来像这样

import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router";
import App from "./App";

const router = createBrowserRouter([
  // ... route definitions
]);

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

你应该将其重命名为 entry.client.tsx 并将其更改为这样

import React from "react";
import ReactDOM from "react-dom/client";
import { HydratedRouter } from "react-router/dom";

ReactDOM.hydrateRoot(
  document,
  <React.StrictMode>
    <HydratedRouter />
  </React.StrictMode>
);
  • 使用 hydrateRoot 而不是 createRoot
  • 渲染 <HydratedRouter> 而不是你的 <App/> 组件
  • 注意:我们不再创建路由并手动将它们传递给 <RouterProvider />。我们将在下一步迁移我们的路由定义。

6. 迁移你的路由

React Router Vite 插件使用 routes.ts 文件来配置你的路由。格式将与你的数据路由器的定义非常相似。

👉 将定义移动到 routes.ts 文件

touch src/routes.ts src/catchall.tsx

将你的路由定义移动到 routes.ts。请注意,架构并不完全匹配,因此你将收到类型错误;我们将在下一步修复此问题。

+import type { RouteConfig } from "@react-router/dev/routes";

-const router = createBrowserRouter([
+export default [
  {
    path: "/",
    lazy: () => import("./routes/layout").then(convert),
    children: [
      {
        index: true,
        lazy: () => import("./routes/home").then(convert),
      },
      {
        path: "about",
        lazy: () => import("./routes/about").then(convert),
      },
      {
        path: "todos",
        lazy: () => import("./routes/todos").then(convert),
        children: [
          {
            path: ":id",
            lazy: () =>
              import("./routes/todo").then(convert),
          },
        ],
      },
    ],
  },
-]);
+] satisfies RouteConfig;

👉 将 lazy 加载器替换为 file 加载器

export default [
  {
    path: "/",
-   lazy: () => import("./routes/layout").then(convert),
+   file: "./routes/layout.tsx",
    children: [
      {
        index: true,
-       lazy: () => import("./routes/home").then(convert),
+       file: "./routes/home.tsx",
      },
      {
        path: "about",
-       lazy: () => import("./routes/about").then(convert),
+       file: "./routes/about.tsx",
      },
      {
        path: "todos",
-       lazy: () => import("./routes/todos").then(convert),
+       file: "./routes/todos.tsx",
        children: [
          {
            path: ":id",
-           lazy: () => import("./routes/todo").then(convert),
+           file: "./routes/todo.tsx",
          },
        ],
      },
    ],
  },
] satisfies RouteConfig;

查看我们的路由配置指南,以了解有关 routes.ts 文件和辅助函数的更多信息,以进一步简化路由定义。

7. 启动应用

此时,你应该已完全迁移到 React Router Vite 插件。继续更新你的 dev 脚本并运行应用,以确保一切正常。

👉 添加 dev 脚本并运行应用

"scripts": {
  "dev": "react-router dev"
}

现在,在继续之前,请确保你可以在此时启动你的应用

npm run dev

你可能需要将 .react-router/ 添加到你的 .gitignore 文件中,以避免在你的存储库中跟踪不必要的文件。

.react-router/

你可以查看 类型安全,以了解如何完全设置和使用为参数、加载器数据等自动生成的类型安全。

启用 SSR 和/或 预渲染

如果你想启用服务端渲染和静态预渲染,你可以使用 bundler 插件中的 ssrprerender 选项来实现。对于 SSR,你还需要将服务器构建部署到服务器。有关更多信息,请参阅 部署

import type { Config } from "@react-router/dev/config";

export default {
  ssr: true,
  async prerender() {
    return ["/", "/about", "/contact"];
  },
} satisfies Config;
文档和示例 CC 4.0