从 v6 升级
如果启用了所有的未来特性标志,那么升级到 v7 时就不会有破坏性的变更。这些标志能让你每次针对一项变更来更新你的应用程序。我们强烈建议你在每完成一个步骤后进行一次提交并发布,而不是一次性完成所有操作。
更新到最新的 v6.x
首先更新到 v6.x 的最新版本,以获取最新的未来特性标志以及控制台警告信息。
👉 更新到最新的 v6 版本。
npm install react-router-dom@6
v7_relativeSplatPath
背景
针对诸如 dashboard/*
(就 *
而言)这类多片段通配符路径,改变其相对路径匹配和链接方式。如需了解更多信息,请查看变更日志。
👉 启用该特性标志
启用该特性标志取决于路由器的类型:
<BrowserRouter
future={{
v7_relativeSplatPath: true,
}}
/>
createBrowserRouter(routes, {
future: {
v7_relativeSplatPath: true,
},
});
更新你的代码
如果你有任何带有路径及通配符(例如 <Route path="dashboard/*">
这样的)的路由,并且其下方存在像 <Link to="relative">
或 <Link to="../relative">
这样的相对链接,那么你将需要更新你的代码。
👉 将 <Route>
拆分成两个
将任何包含多片段通配符的 <Route>
拆分成一个带有路径的父路由以及一个带有通配符的子路由:
<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="../">Dashboard Home</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>
);
}
v7_startTransition
背景
此项功能使用 React.useTransition
而非 React.useState
来进行路由器状态更新。如需了解更多信息,请查看变更日志。
👉 启用该特性标志
<BrowserRouter
future={{
v7_startTransition: true,
}}
/>
// or
<RouterProvider
future={{
v7_startTransition: true,
}}
/>
👉 更新你的代码
除非你在组件内部使用了 React.lazy
,否则你无需进行任何更新。
在组件内部使用 React.lazy
与 React.useTransition
(或其他在组件内部创建 promises 的代码)是不兼容的。将 React.lazy
移到模块作用域中,并停止在组件内部创建 promises。这并非是 React Router
的限制,而是 React
的错误用法导致的。
v7_fetcherPersist
提示:
如果你没有使用 <RouterProvider>
,则可以跳过此项。
背景
现在,获取器(fetcher)的生命周期基于它何时返回到一个空闲状态,而非其所属组件卸载之时。如需了解更多信息,请查看变更日志。
👉 启用该特性标志
createBrowserRouter(routes, {
future: {
v7_fetcherPersist: true,
},
});
更新你的代码
它不太可能会影响到你的应用程序。你可能需要检查一下 useFetchers
的各种使用情况,因为它们的存续时间可能会比之前更长。取决于你正在进行的操作,你渲染某些内容的时长可能会比之前更久。
v7_normalizeFormMethod
提示:
如果你没有使用 <RouterProvider>
,则可以跳过此项。
这会将 formMethod
字段规范化为大写的 HTTP 方法,以与 fetch()
函数的行为保持一致。如需了解更多信息,请查看变更日志。
👉 启用该特性标志
createBrowserRouter(routes, {
future: {
v7_normalizeFormMethod: true,
},
});
更新你的代码
如果你的任何代码正在检查小写的 HTTP 方法,那么你将需要对其进行更新,改为检查大写的 HTTP 方法(或者对其调用 toLowerCase()
方法来进行转换后再检查)。
👉 将 formMethod
与大写形式进行对比。
useNavigation().formMethod === "post"
useFetcher().formMethod === "get";
useNavigation().formMethod === "POST"
useFetcher().formMethod === "GET";
v7_partialHydration
提示:
如果你没有使用 <RouterProvider>
,则可以跳过此项。
这使得服务器端渲染(SSR)框架能够仅提供部分 hydration(水合作用,此处指在 SSR 中关联服务器端渲染内容与客户端交互等相关操作)数据。你不太可能需要为此担心,只需开启相应特性标志就行。如需了解更多信息,请查看变更日志。
👉 启用该特性标志
createBrowserRouter(routes, {
future: {
v7_partialHydration: true,
},
});
更新你的代码
在使用部分水合(hydration)的情况下,你需要提供一个 HydrateFallback
组件,以便在初始水合期间进行渲染。此外,如果你之前使用过 fallbackElement
,那么需要将其移除,因为它现在已被弃用。在大多数情况下,你会希望将之前的 fallbackElement
重新用作 HydrateFallback
组件。
👉 用 HydrateFallback
替换 fallbackElement
const router = createBrowserRouter(
[
{
path: "/",
Component: Layout,
HydrateFallback: Fallback,
// or
hydrateFallbackElement: <Fallback />,
children: [],
},
],
);
<RouterProvider
router={router}
fallbackElement={<Fallback />}
/>
v7_skipActionErrorRevalidation
提示:
如果你没有使用 createBrowserRouter
,则可以跳过此项。
当启用此特性标志后,在 action 抛出或返回状态码为 4xx
/5xx
的 Response
,加载器(loaders)默认将不再重新验证。在这些情况下,你可以通过 shouldRevalidate
以及 actionStatus
参数来选择重新进行验证。
👉 启用该特性标志
createBrowserRouter(routes, {
future: {
v7_skipActionErrorRevalidation: true,
},
});
更新你的代码
在大多数情况下,你可能无需对应用程序代码进行更改。通常来说,如果某个 action 出现错误,数据不太可能已被修改,也就不需要重新验证。如果你的任何代码在 action 出错的场景下确实修改了数据,那你有两个选择:
👉 选项1:修改 action
,以避免在出错场景下进行数据变更
// 修改前
async function action() {
await mutateSomeData();
if (detectError()) {
throw new Response(error, { status: 400 });
}
await mutateOtherData();
// ...
}
// 修改后
async function action() {
if (detectError()) {
throw new Response(error, { status: 400 });
}
// 现在所有数据在验证后才开始变更
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) {
// 当 actions 返回 4xx/5xx 状态时重新验证此加载器
return true;
}
return defaultShouldRevalidate;
}
弃用说明
json
和 defer
方法已被弃用,建议改为返回原始对象。
async function loader() {
return json({ data });
return { data };
如果你之前使用 json
方法将数据序列化为JSON格式,那么现在可以改用原生的 Response.json()
方法来替代。
更新到 v7
既然你的应用程序已经完成了上述相关更新(与前文提到的更新内容保持同步),那么理论上你就可以毫无问题地直接更新到版本 7 了。
👉 安装 v7 版本
npm install react-router-dom@latest
👉 用 react-router 替换掉 react-router-dom
在版本 7 中,我们不再需要 “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";