Skip to content

从 Remix 升级

React Router 版本 7 是 Remix 在版本 2 之后的下一个主要版本(如需了解更多信息,请查看我们的“迈向 React 19 的渐进式历程”一文)。

如果你已经启用了所有 Remix 版本 2 的未来特性标志,那么从 Remix 版本 2 升级到 React Router 版本 7 主要涉及更新依赖项。

提示:

步骤 2 到 8 中的大部分内容都可以使用社区成员 James Restall 创建的代码转换工具 codemod 来自动更新。

1. 使用未来特性

👉 使用未来特性

在你的 Remix v2 应用程序中采用所有现有的未来特性

2. 更新依赖

大多数过去通过特定运行时软件包(如 @remix-run/node@remix-run/cloudflare 等)重新导出的“共享”API,在版本 7 中都已整合进了 react-router 里。因此,你不用再从 @react-router/node@react-router/cloudflare 中导入了,而是直接从 react-router 中导入它们。

ts
import { redirect } from "@remix-run/node"; 
import { redirect } from "react-router"; 

在版本 7 中,你唯一应该从特定运行时软件包中导入的 API,是那些特定于该运行时的 API,比如适用于 Node 的 createFileSessionStorage 以及适用于 Cloudflare 的 createWorkersKVSessionStorage

👉 运行代码转换工具(自动化操作)

你可以使用以下代码转换工具来自动更新你的软件包和导入语句。这个代码转换工具会更新你所有的软件包和导入内容。在运行该代码转换工具之前,务必提交所有未完成的更改,以防你后续需要进行回滚操作。

sh
npx codemod remix/2/react-router/upgrade

👉 安装依赖

sh
npm install # or npm i

👉 更新依赖(手动)

如果你不喜欢使用 codemod,你可以手动更新你的依赖。

展开按字母顺序查看软件包名称变更的列表
Remix v2 PackageReact Router v7 Package
@remix-run/architect➡️@react-router/architect
@remix-run/cloudflare➡️@react-router/cloudflare
@remix-run/dev➡️@react-router/dev
@remix-run/express➡️@react-router/express
@remix-run/fs-routes➡️@react-router/fs-routes
@remix-run/node➡️@react-router/node
@remix-run/react➡️react-router
@remix-run/route-config➡️@react-router/dev
@remix-run/routes-option-adapter➡️@react-router/remix-routes-option-adapter
@remix-run/serve➡️@react-router/serve
@remix-run/server-runtime➡️react-router
@remix-run/testing➡️react-router

3. 更改 package.json 文件中的脚本

提示:

如果你使用的是 codemod,就可以跳过这一步,因为这一步已被自动完成了。

👉 更新 package.json 中的脚本

ScriptRemix v2React Router v7
devremix vite:dev➡️react-router dev
buildremix vite:build➡️react-router build
startremix-serve build/server/index.js➡️react-router-serve build/server/index.js
typechecktsc➡️react-router typegen && tsc

4. 新建 routes.ts 文件

提示:

如果你使用了代码转换工具并且启用了 Remix v2 的 v3_routeConfig 标志,就可以跳过这一步,因为这一步已经被自动完成了。

在 React Router 版本 7 中,你可以使用 app/routes.ts 文件来定义路由。查看路由相关文档以获取更多信息。

👉 更新依赖项(如果使用了 Remix v2 的 v3_routeConfig 特性)

ts
// app/routes.ts
import { type RouteConfig } from "@remix-run/route-config"; 
import { flatRoutes } from "@remix-run/fs-routes"; 
import { remixRoutesOptionAdapter } from "@remix-run/routes-option-adapter"; 
import { type RouteConfig } from "@react-router/dev/routes"; 
import { flatRoutes } from "@react-router/fs-routes"; 
import { remixRoutesOptionAdapter } from "@react-router/remix-routes-option-adapter"; 

export default [
  // 无论你的路由是如何定义的
] satisfies RouteConfig;

👉 新建一个 routes.ts 文件(如果没有使用 Remix v2 的 v3_routeConfig 特性)

sh
touch app/routes.ts

为了实现向后兼容,也为了照顾那些更喜欢基于文件的约定的用户,你可以通过新的 @react-router/fs-routes 软件包,选择使用与在Remix v2中相同的“扁平路由”约定。

ts
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";

export default flatRoutes() satisfies RouteConfig;

或者,如果你之前是使用 routes 选项来定义基于配置的路由的话:

ts
import { type RouteConfig } from "@react-router/dev/routes";
import { remixRoutesOptionAdapter } from "@react-router/remix-routes-option-adapter";

export default remixRoutesOptionAdapter((defineRoutes) => {
  return defineRoutes((route) => {
    route("/", "home/route.tsx", { index: true });
    route("about", "about/route.tsx");
    route("", "concerts/layout.tsx", () => {
      route("trending", "concerts/trending.tsx");
      route(":city", "concerts/city.tsx");
    });
  });
}) satisfies RouteConfig;

如果你之前在 vite.config.ts 文件中使用了 routes 选项,务必要将其移除。

ts
export default defineConfig({
  plugins: [
    remix({
      ssr: true,
      ignoredRouteFiles: ['**/*'], 
      routes(defineRoutes) {  
        return defineRoutes((route) => { 
          route("/somewhere/cool/*", "catchall.tsx"); 
        }); 
      },  
    })
    tsconfigPaths(),
  ],
});

5. 新建 React Router 配置文件

👉 在你项目中新增 react-router.config.ts 文件

之前传递给 vite.config.ts 文件中 Remix 插件的配置,现在从 react-router.config.ts 文件中导出了。

注意:此时,你应该移除在步骤 1 中添加的 v3 未来特性标志。

sh
touch react-router.config.ts
ts
// vite.config.ts
export default defineConfig({
  plugins: [
    remix({ 
      ssr: true, 
      future: {/* 所有 v3 特性 */} 
    }), 
    remix(), 
    tsconfigPaths(),
  ],
});

// react-router.config.ts
import type { Config } from "@react-router/dev/config"; 
export default { 
  ssr: true, 
} satisfies Config; 

6. 在 vite.config 添加 Router 插件

提示:

如果你使用了代码转换工具,就可以跳过这一步,因为这一步已经被自动完成了。

👉 添加 reactRouter 插件到 vite.config

更改 vite.config.ts 文件,以便从 @react-router/dev/vite 导入并使用新的 reactRouter 插件。

ts
import { vitePlugin as remix } from "@remix-run/dev"; 
import { reactRouter } from "@react-router/dev/vite"; 
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
  plugins: [
    remix(), 
    reactRouter(), 
    tsconfigPaths(),
  ],
});

7. 开启类型安全

提示:

如果你没使用 TypeScript,可以跳过这一步。

React Router 会自动在你的应用程序根目录下的 .react-router/ 目录中为你的路由模块生成类型。该目录完全由 React Router 管理,应当被添加到 .gitignore 文件中(即让 Git 忽略它)。了解更多关于新的类型安全特性的内容。

👉 添加 .react-router/.gitignore

.react-router/

👉 更新 tsconfig.json

更新你的 tsconfig.json 文件中的 types 字段,使其包含以下内容:

  • include 字段中添加 .react-router/types/**/* 路径
  • types 字段中添加相应的 @react-router/* 软件包
  • 添加 rootDirs 以简化相对导入
json
{
  "include": [
    /* ... */
    ".react-router/types/**/*"
  ],
  "compilerOptions": {
    "types": ["@remix-run/node", "vite/client"], 
    "types": ["@react-router/node", "vite/client"], 
    /* ... */
    "rootDirs": [".", "./.react-router/types"] 
  }
}

8. 重命名入口文件中的组件

提示:

如果你使用的是 codemod,就可以跳过这一步,因为这一步已被自动完成了。

如果你的应用程序中有 entry.server.tsx 和/或 entry.client.tsx 文件,那么你将需要更新这些文件中的主要组件。

tsx
import { RemixServer } from "@remix-run/react"; 
import { ServerRouter } from "react-router"; 

<RemixServer context={remixContext} url={request.url} />, 
<ServerRouter context={remixContext} url={request.url} />,  
tsx
import { RemixBrowser } from "@remix-run/react"; 
import { HydratedRouter } from "react-router/dom"; 

hydrateRoot(
  document,
  <StrictMode>
    <RemixBrowser />
    <HydratedRouter />
  </StrictMode>,
);

9. 更新 AppLoadContext 的类型

提示:

如果你之前使用的是 remix-serve,那么可以跳过这一步。只有当你在 Remix v2 中使用自定义服务器时,这一步才适用。

由于 React Router 既可以用作 React 框架,也可以用作独立的路由库,所以 LoaderFunctionArgs(加载器函数参数)和 ActionFunctionArgs(操作函数参数)中的 context 参数现在是可选的,并且默认情况下其类型为 any。你可以为你的加载上下文注册类型,以便为你的加载器和操作获取类型安全保障。

👉 给你的加载上下文注册类型

在迁移到新的 Route.LoaderArgsRoute.ActionArgs 类型之前,你可以暂时使用你的加载上下文类型来扩充 LoaderFunctionArgsActionFunctionArgs,以便更轻松地进行迁移。

ts
declare module "react-router" {
  // 你在v2版本中使用的 `AppLoadContext`
  interface AppLoadContext {
    whatever: string;
  }

  // TODO: 一旦我们已将加载器迁移到使用 Route.LoaderArgs,就移除这个
  interface LoaderFunctionArgs {
    context: AppLoadContext;
  }

  // TODO: 一旦我们已将操作迁移为使用 `Route.ActionArgs`,就移除这个
  interface ActionFunctionArgs {
    context: AppLoadContext;
  }
}

export {}; // 对于 TypeScript 来说,将其当作一个模块来处理是很有必要的

提示:

使用 declare module 来注册类型是一种标准的 TypeScript 技术,被称作模块扩充。你可以在 tsconfig.json 文件的 include 字段涵盖的任何 TypeScript 文件中进行此操作,但我们建议在你的应用目录内使用专门的 env.ts 文件来操作。

👉 使用新类型

一旦你采用了新的类型生成方式,就可以移除对 LoaderFunctionArgs/ActionFunctionArgs 的扩充内容,并改为使用 Route.LoaderArgsRoute.ActionArgs 中的 context 参数。

ts
declare module "react-router" {
  // 你在v2版本中使用的 `AppLoadContext`
  interface AppLoadContext {
    whatever: string;
  }
}

export {}; // 对于 TypeScript 来说,将其当作一个模块来处理是很有必要的
tsx
import type { Route } from "./+types/my-route";

export function loader({ context }: Route.LoaderArgs) {}
// { whatever: string }  ^^^^^^^

export function action({ context }: Route.ActionArgs) {}
// { whatever: string }  ^^^^^^^

恭喜你!你现在已经使用 React Router v7 了。继续运行你的应用程序,确保一切都按预期运行。

Released under the MIT License.