React Router v7 需要以下最低版本
node@20
react@18
react-dom@18
如果您已启用所有未来标志,v7 升级将没有重大更改。这些标志允许您一次一个地更新您的应用程序。我们强烈建议您在每一步之后进行一次提交并发布,而不是一次性完成所有操作。
首先更新到 v6.x 的最新次要版本,以获取最新的未来标志和控制台警告。
👉 更新到最新的 v6
npm install react-router-dom@6
背景
更改了多段 splat 路径(如 dashboard/*
,而不仅仅是 *
)的相对路径匹配和链接。查看 CHANGELOG 了解更多信息。
👉 启用该标志
启用该标志取决于路由器的类型
<BrowserRouter
future={{
v7_relativeSplatPath: true,
}}
/>
createBrowserRouter(routes, {
future: {
v7_relativeSplatPath: true,
},
});
更新您的代码
如果您有任何带有路径 + splat 的路由,如 <Route path="dashboard/*">
,并且其下方有相对链接,如 <Link to="relative">
或 <Link to="../relative">
,您将需要更新您的代码。
👉 将 <Route>
拆分为两个
将任何多段 splat 的 <Route>
拆分为一个带有路径的父路由和一个带有 splat 的子路由
<Routes>
<Route path="/" element={<Home />} />
- <Route path="dashboard/*" element={<Dashboard />} />
+ <Route path="dashboard">
+ <Route path="*" element={<Dashboard />} />
+ </Route>
</Routes>
// or
createBrowserRouter([
{ path: "/", element: <Home /> },
{
- path: "dashboard/*",
- element: <Dashboard />,
+ path: "dashboard",
+ children: [{ path: "*", element: <Dashboard /> }],
},
]);
👉 更新相对链接
更新该路由树中的任何 <Link>
元素,以包含额外的 ..
相对段,以继续链接到相同的位置
function Dashboard() {
return (
<div>
<h2>Dashboard</h2>
<nav>
<Link to="/">Dashboard Home</Link>
- <Link to="team">Team</Link>
- <Link to="projects">Projects</Link>
+ <Link to="../team">Team</Link>
+ <Link to="../projects">Projects</Link>
</nav>
<Routes>
<Route path="/" element={<DashboardHome />} />
<Route path="team" element={<DashboardTeam />} />
<Route
path="projects"
element={<DashboardProjects />}
/>
</Routes>
</div>
);
}
背景
这将使用 React.useTransition
而不是 React.useState
进行路由器状态更新。请查看 CHANGELOG 了解更多信息。
👉 启用该标志
<BrowserRouter
future={{
v7_startTransition: true,
}}
/>
// or
<RouterProvider
future={{
v7_startTransition: true,
}}
/>
👉 更新您的代码
除非您在组件内部使用 React.lazy
,否则您不需要更新任何内容。
在组件内部使用 React.lazy
与 React.useTransition
(或在组件内部创建 promise 的其他代码)不兼容。将 React.lazy
移到模块作用域,并停止在组件内部创建 promise。这不是 React Router 的限制,而是对 React 的不正确使用。
<RouterProvider>
,可以跳过此部分
背景
fetcher 的生命周期现在基于其返回到空闲状态的时间,而不是其所有者组件卸载的时间:查看 CHANGELOG 了解更多信息。
启用标志
createBrowserRouter(routes, {
future: {
v7_fetcherPersist: true,
},
});
更新您的代码
这不太可能影响您的应用程序。您可能需要检查任何 useFetchers
的用法,因为它们可能比以前持续更长的时间。根据您正在做的事情,您可能会渲染比以前更长时间的内容。
<RouterProvider>
,可以跳过此部分
这将 formMethod
字段规范化为大写的 HTTP 方法,以与 fetch()
的行为保持一致。查看 CHANGELOG 了解更多信息。
👉 启用标志
createBrowserRouter(routes, {
future: {
v7_normalizeFormMethod: true,
},
});
更新您的代码
如果您的任何代码正在检查小写的 HTTP 方法,您需要更新它以检查大写的 HTTP 方法(或在其上调用 toLowerCase()
)。
👉 将 formMethod
与大写字母进行比较
-useNavigation().formMethod === "post"
-useFetcher().formMethod === "get";
+useNavigation().formMethod === "POST"
+useFetcher().formMethod === "GET";
<RouterProvider>
,可以跳过此部分
这将启用数据路由器的部分注水,主要用于 SSR 框架,但如果您正在使用 lazy
加载路由模块,它也很有用。您不太可能需要担心这个,只需打开标志即可。查看 CHANGELOG 了解更多信息。
👉 启用标志
createBrowserRouter(routes, {
future: {
v7_partialHydration: true,
},
});
更新您的代码
通过部分注水,您需要提供一个 HydrateFallback
组件以在初始注水期间进行渲染。此外,如果您以前使用 fallbackElement
,则需要将其删除,因为它现在已被弃用。在大多数情况下,您会希望将 fallbackElement
重用为 HydrateFallback
。
👉 将 fallbackElement
替换为 HydrateFallback
const router = createBrowserRouter(
[
{
path: "/",
Component: Layout,
+ HydrateFallback: Fallback,
// or
+ hydrateFallbackElement: <Fallback />,
children: [],
},
],
);
<RouterProvider
router={router}
- fallbackElement={<Fallback />}
/>
createBrowserRouter
,可以跳过此部分
当此标志启用时,在操作抛出/返回状态码为 4xx
/5xx
的 Response
后,加载器将不再默认重新验证。您可以通过 shouldRevalidate
和 actionStatus
参数在这些场景中选择加入重新验证。
👉 启用标志
createBrowserRouter(routes, {
future: {
v7_skipActionErrorRevalidation: true,
},
});
更新您的代码
在大多数情况下,您可能不必对应用程序代码进行更改。通常,如果操作出错,数据不太可能发生变异并需要重新验证。如果您的任何代码确实在操作错误场景中变异数据,您有两个选择
👉 选项 1:更改 action
以避免在错误场景中发生变异
// Before
async function action() {
await mutateSomeData();
if (detectError()) {
throw new Response(error, { status: 400 });
}
await mutateOtherData();
// ...
}
// After
async function action() {
if (detectError()) {
throw new Response(error, { status: 400 });
}
// All data is now mutated after validations
await mutateSomeData();
await mutateOtherData();
// ...
}
👉 选项 2:通过 shouldRevalidate
和 actionStatus
选择加入重新验证
async function action() {
await mutateSomeData();
if (detectError()) {
throw new Response(error, { status: 400 });
}
await mutateOtherData();
}
async function loader() { ... }
function shouldRevalidate({ actionStatus, defaultShouldRevalidate }) {
if (actionStatus != null && actionStatus >= 400) {
// Revalidate this loader when actions return a 4xx/5xx status
return true;
}
return defaultShouldRevalidate;
}
json
和 defer
方法已弃用,建议返回原始对象。
async function loader() {
- return json({ data });
+ return { data };
如果您正在使用 json
将数据序列化为 JSON,您可以改用原生的 Response.json() 方法。
现在您的应用程序已经跟上进度,您可以(理论上!)毫无问题地直接更新到 v7。
👉 安装 v7
npm install react-router-dom@latest
👉 用 react-router 替换 react-router-dom
在 v7 中,我们不再需要 "react-router-dom"
,因为包已经简化。您可以从 "react-router"
导入所有内容
npm uninstall react-router-dom
npm install react-router@latest
注意,您只需要在 package.json 中保留 "react-router"
。
👉 更新导入
现在您应该更新您的导入以使用 react-router
-import { useLocation } from "react-router-dom";
+import { useLocation } from "react-router";
您可以使用此命令来代替手动更新导入。不过请确保您的 git 工作树是干净的,以便在出现问题时可以恢复。
find ./path/to/src \( -name "*.tsx" -o -name "*.ts" -o -name "*.js" -o -name "*.jsx" \) -type f -exec sed -i '' 's|from "react-router-dom"|from "react-router"|g' {} +
如果您安装了 GNU sed
(大多数 Linux 发行版),请改用此命令
find ./path/to/src \( -name "*.tsx" -o -name "*.ts" -o -name "*.js" -o -name "*.jsx" \) -type f -exec sed -i 's|from "react-router-dom"|from "react-router"|g' {} +
👉 更新 DOM 特定的导入
RouterProvider
和 HydratedRouter
来自深层导入,因为它们依赖于 "react-dom"
-import { RouterProvider } from "react-router-dom";
+import { RouterProvider } from "react-router/dom";
注意,对于非 DOM 上下文(例如 Jest 测试),您应该使用顶层导入
-import { RouterProvider } from "react-router-dom";
+import { RouterProvider } from "react-router";
恭喜,您现在使用的是 v7!