从 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 中导入它们。
import { redirect } from "@remix-run/node";
import { redirect } from "react-router"; 在版本 7 中,你唯一应该从特定运行时软件包中导入的 API,是那些特定于该运行时的 API,比如适用于 Node 的 createFileSessionStorage 以及适用于 Cloudflare 的 createWorkersKVSessionStorage。
👉 运行代码转换工具(自动化操作)
你可以使用以下代码转换工具来自动更新你的软件包和导入语句。这个代码转换工具会更新你所有的软件包和导入内容。在运行该代码转换工具之前,务必提交所有未完成的更改,以防你后续需要进行回滚操作。
npx codemod remix/2/react-router/upgrade👉 安装依赖
npm install # or npm i👉 更新依赖(手动)
如果你不喜欢使用 codemod,你可以手动更新你的依赖。
展开按字母顺序查看软件包名称变更的列表
| Remix v2 Package | React 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 中的脚本
| Script | Remix v2 | React Router v7 | |
|---|---|---|---|
dev | remix vite:dev | ➡️ | react-router dev |
build | remix vite:build | ➡️ | react-router build |
start | remix-serve build/server/index.js | ➡️ | react-router-serve build/server/index.js |
typecheck | tsc | ➡️ | react-router typegen && tsc |
4. 新建 routes.ts 文件
提示:
如果你使用了代码转换工具并且启用了 Remix v2 的 v3_routeConfig 标志,就可以跳过这一步,因为这一步已经被自动完成了。
在 React Router 版本 7 中,你可以使用 app/routes.ts 文件来定义路由。查看路由相关文档以获取更多信息。
👉 更新依赖项(如果使用了 Remix v2 的 v3_routeConfig 特性)
// 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 特性)
touch app/routes.ts为了实现向后兼容,也为了照顾那些更喜欢基于文件的约定的用户,你可以通过新的 @react-router/fs-routes 软件包,选择使用与在Remix v2中相同的“扁平路由”约定。
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
export default flatRoutes() satisfies RouteConfig;或者,如果你之前是使用 routes 选项来定义基于配置的路由的话:
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 选项,务必要将其移除。
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 未来特性标志。
touch react-router.config.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 插件。
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以简化相对导入
{
"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 文件,那么你将需要更新这些文件中的主要组件。
import { RemixServer } from "@remix-run/react";
import { ServerRouter } from "react-router";
<RemixServer context={remixContext} url={request.url} />,
<ServerRouter context={remixContext} url={request.url} />, 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.LoaderArgs 和 Route.ActionArgs 类型之前,你可以暂时使用你的加载上下文类型来扩充 LoaderFunctionArgs 和 ActionFunctionArgs,以便更轻松地进行迁移。
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.LoaderArgs 和 Route.ActionArgs 中的 context 参数。
declare module "react-router" {
// 你在v2版本中使用的 `AppLoadContext`
interface AppLoadContext {
whatever: string;
}
}
export {}; // 对于 TypeScript 来说,将其当作一个模块来处理是很有必要的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 了。继续运行你的应用程序,确保一切都按预期运行。