React Router 如何按 v8 最低版本要求与 future 标志分步从 v7 升级到 v8?
【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router
如果你的 React Router 应用还停在 v7,想升到 v8,官方升级指南 Upgrading from v7 给出的思路是:先在 v7 上把最低依赖版本提上去、逐个开启 v8 的 future 标志并改好相关代码,最后再执行一次npm install跳到 v8。官方明确建议每完成一步就提交一次代码并发布,而不是全部改完再一次性上线——大多数标志可以按任意顺序采纳,文档中会标注例外。适用对象包括 Framework、Data、Declarative 三种模式的项目(模式区别),本文会按模式区分每个步骤是否适用。
第一步:升级到最新的 v7.x
在开启任何 future 标志或做调用点改动之前,先把react-router升到 v7 的次新版本,确保能拿到全部 v8 相关的标志:
npm install react-router@7 @react-router/{dev,node,etc.}@7其中@react-router/{dev,node,etc.}是文档中的占位写法,代表你实际在用的配套包,比如@react-router/dev、@react-router/node,按项目里实际安装的替换。升级过程中可能出现一批 deprecation 警告,这是预期现象,文档说明这些警告对应下面各节要处理的变更。
第二步:核对 v8 的最低版本要求
Upgrading from v7 的 Minimum Versions 一节列出:
| 依赖 | 最低版本 | 适用模式 |
|---|---|---|
| Node | 22.22+ | framework、data、declarative |
| React / react-dom | 19.2.7+ | framework、data、declarative |
| Vite | 7+(且需要开启future.v8_viteEnvironmentApi) | 仅 framework 模式 |
文档同时要求:Framework 模式下要确认项目里任何自定义的 Vite 插件或配置都兼容 Vite 7。这些依赖要在升级 React Router 到 v8 之前更新完成。
第三步:逐个开启 v8 的 future 标志
标志机制是 API 开发策略 的一部分:破坏性变更先通过 future flag 引入,开启后行为立即切换,不开启则应用不受影响。以下按 v7 文档的顺序列出,每个标志标注了适用模式。
future.v8_middleware(framework、data 模式)
Middleware 让你能在匹配路径的Response生成前后运行代码,支撑认证、日志、错误处理等可复用逻辑,用法见 Middleware 文档。
Framework 模式在react-router.config.ts中开启:
import type { Config } from "@react-router/dev/config"; export default { future: { v8_middleware: true, }, } satisfies Config;Data 模式则在创建路由器时开启:
import { createBrowserRouter } from "react-router"; const router = createBrowserRouter(routes, { future: { v8_middleware: true, }, });代码改动方面,v7 文档给出的判断依据是:如果你只在loader和action里使用了context参数——Framework 模式下,使用react-router-serve的项目通常不需要改动;只有自定义了带getLoadContext的服务器才需要按 middleware 文档迁移到新 API。Data 模式下,需要按 middleware 文档补充Future模块的 TypeScript 类型增强,让context获得正确类型。
future.v8_splitRouteModules(framework 模式)
把clientLoader、clientAction、clientMiddleware、HydrateFallback等客户端路由导出拆成独立 chunk,让它们在组件代码还在下载时就能获取并执行。设为true是选择性开启;设为"enforce"则强制所有路由必须可拆分,因共享代码无法拆分的路由会导致构建失败。
import type { Config } from "@react-router/dev/config"; export default { future: { v8_splitRouteModules: true, }, } satisfies Config;文档说明此标志是纯优化功能,开启后无需任何代码改动。
future.v8_viteEnvironmentApi(framework 模式)
启用 Vite Environment API 支持(文档说明此标志仅在 Vite 6+ 可用,而 Framework 模式升到 v8 的最低要求是vite@7+且依赖此标志)。多数用户不需要改动;如果你的自定义 Vite 配置依赖旧的isSsrBuild参数——例如自定义服务器构建里设置build.rollupOptions.input——需要把这些配置移到按环境的 Environment API 配置下。v7 文档给出的 diff 示例:
import { reactRouter } from "@react-router/dev/vite"; import { defineConfig } from "vite"; -export default defineConfig(({ isSsrBuild }) => ({ - build: { - rollupOptions: isSsrBuild - ? { - input: "./server/app.ts", - } - : undefined, - }, +export default defineConfig({ + environments: { + ssr: { + build: { + rollupOptions: { + input: "./server/app.ts", + }, + }, + }, + }, plugins: [reactRouter()], -})); +});即把顶层build里的 SSRrollupOptions移到environments.ssr.build下,isSsrBuild参数不再使用。
future.v8_passThroughRequests(framework 模式)
默认情况下 React Router 会规范化传给loader、action、middleware的request.url,去掉.data后缀和?index、?_routes这类内部参数。开启该标志后直接透传原始 HTTPRequest,好处是减少关键路径上的new Request()调用,并且可以在 handler 里通过 URL 是否带.data后缀区分 document 请求和数据请求。
import type { Config } from "@react-router/dev/config"; export default { future: { v8_passThroughRequests: true, }, } satisfies Config;如果你的代码依赖检查request.url,要排查对 URL 格式的假设。v7 文档给出前后对照(Before 是旧行为,After 是开启标志后的写法):
// ❌ Before: assuming no `.data` suffix in `request.url` pathname export async function loader({ request, }: Route.LoaderArgs) { let url = new URL(request.url); if (url.pathname === "/path") { // This check might now behave differently because the request pathname will // contain the `.data` suffix on data requests } } // ✅ After: use `url` for normalized routing logic and `request.url` // for raw routing logic export async function loader({ request, url, }: Route.LoaderArgs) { if (url.pathname === "/path") { // This will always have the `.data` suffix stripped } // And now you can distinguish between document versus data requests let isDataRequest = new URL( request.url, ).pathname.endsWith(".data"); }即:规范化的路由判断改用新的url参数(一个去掉了.data后缀的URL实例),原始请求特征判断才用request.url。
future.v8_trailingSlashAwareDataRequests(framework 模式)
Framework 模式从.dataURL 提供数据请求。此前带尾斜杠与不带尾斜杠的路由可能映射到同一个.dataURL,因为生成 URL 时没有考虑尾斜杠。该标志让数据请求 URL 保留尾斜杠语义,避免应用区分/a/b/c与/a/b/c/时产生歧义。
import type { Config } from "@react-router/dev/config"; export default { future: { v8_trailingSlashAwareDataRequests: true, }, } satisfies Config;开启后,/a/b/c/这类带尾斜杠路由的数据请求从/a/b/c.data变为新的/a/b/c/_.data格式;根路由的数据请求也从/_root.data变为/_.data。文档给出的完整对照(以/a/b/c与/a/b/c/两个 URL 为例):
URL/a/b/c | HTTP pathname | requestpathname |
|---|---|---|
| Document | /a/b/c | /a/b/c |
| Data | /a/b/c.data | /a/b/c |
URL/a/b/c/(开启标志后) | HTTP pathname | requestpathname |
|---|---|---|
| Document | /a/b/c/ | /a/b/c/ |
| Data | /a/b/c/_.data | /a/b/c/ |
代码改动点只有一处:如果你的应用、CDN、缓存或 rewrite 规则会匹配.data请求 URL,要更新它们以处理新的_.data格式。
第四步:处理不受标志控制的其他破坏性变更
以下变更没有 future flag 开关,但都允许你在 v7 上先把代码改好(Upgrading from v7 的 "Other Breaking Changes" 一节)。
meta/matches的data改为loaderData(framework 模式)
v8 在几个位置移除了废弃的data字段,改用loaderData:meta函数的data参数、meta函数的matches参数(matches[i].data)、以及useMatches()的返回值。
meta函数中把data换成loaderData:
export function meta({ - data, + loaderData, matches, }: Route.MetaArgs) { return [ { - title: data.title, + title: loaderData.title, }, ]; }读取父级匹配的数据时同样替换:
export function meta({ matches }: Route.MetaArgs) { let rootMatch = matches.find((match) => match.id === "root"); - let rootData = rootMatch?.data; + let rootData = rootMatch?.loaderData; return [{ title: rootData?.siteTitle }]; }useMatches()的调用处:
export default function Component({ matches, loaderData }: ComponentProps) { let matches = useMatches(); - const rootLoaderData = matches[0].data; + const rootLoaderData = matches[0].loaderData; // ... }移除react-router-dom包(framework、data、declarative 模式)
v8 移除react-router-dom这个 re-export 包:DOM 相关 API 从react-router/dom导入,其余全部从react-router导入。
npm uninstall react-router-dom-import { Link, useLocation } from "react-router-dom"; +import { Link, useLocation } from "react-router";DOM 专属 API 走react-router/dom:
-import { RouterProvider } from "react-router-dom"; +import { RouterProvider } from "react-router/dom";Cloudflare Vite 插件(framework 模式)
v8 移除了 React Router 内置的 Cloudflare dev proxy,Cloudflare 项目改用语义为cloudflare的官方插件。把cloudflareDevProxy替换为cloudflare:
import { reactRouter } from "@react-router/dev/vite"; -import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare"; +import { cloudflare } from "@cloudflare/vite-plugin"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ - cloudflareDevProxy(), + cloudflare(), reactRouter(), ], });@react-router/architect的useRequestContextDomainName(framework 模式)
v7 中 architect adapter 创建request时使用X-Forwarded-Host(回退到Host头);v8 默认改用event.requestContext.domainName(回退到Host头)。想在 v7 上提前采纳 v8 行为,就传入useRequestContextDomainName: true:
import { createRequestHandler } from "@react-router/architect"; import * as build from "./build/server"; export const handler = createRequestHandler({ build, useRequestContextDomainName: true, });该选项在 v8 中会被移除,因为event.requestContext.domainName行为成为默认。
第五步:升级 React Router 到 v8
前面的依赖、标志和代码改动都完成后,执行最终安装。data / declarative 模式:
npm install react-router@latestframework 模式:
npm install react-router@latest @react-router/{dev,node,etc.}@latest同样,{dev,node,etc.}替换为你实际使用的@react-router/*包。
验证方式与限制
- 文档对每个阶段的"完成判定"是流程性的:升级 v7.x 时留意新出现的 deprecation 警告(对应上文各节);每个 future 标志和代码改动单独提交并跑通后再继续下一个,官方推荐 "make a commit after each step and ship it"。
- 标志顺序基本任意,但 v7 文档把
future.v8_viteEnvironmentApi与 Vite 7 绑定(Framework 模式的vite@7+明确要求该标志),所以 Framework 模式应把它和 Vite 升级放在同一批处理。 v8_splitRouteModules设为"enforce"时,无法拆分的路由会直接构建失败,这是文档明确给出的失败判定;不确定时先保持true。- 本文不涉及 v8 之后的升级:Future Changes 目前只预告了 v9 的最低版本(
node@24+)和一个尚无已知计划的 breaking changes 列表,属于另一个升级周期。
【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考