主分支
分支
主分支 (6.23.1)开发分支
版本
6.23.1v4/5.xv3.x
从 v5 升级
本页内容

从 v5 升级

向后兼容包

与其一次性升级并更新所有代码(这非常困难且容易出错),不如使用向后兼容包,通过并行运行 v5 和 v6,一次升级一个组件、一个钩子和一条路由。您未触碰的任何代码仍然运行与之前完全相同的代码。一旦所有组件都完全使用 v6 API,您的应用程序就不再需要兼容包,并且在 v6 上运行。官方指南可以在这里找到 这里

我们建议使用向后兼容包来升级具有多个路由的应用程序。否则,我们希望本指南能帮助您一次性完成升级!

介绍

React Router 版本 6 引入了几个强大的新功能,以及与最新版本的 React 的改进兼容性。它还引入了与版本 5 的一些重大更改。本文档是关于如何将您的 v4/5 应用程序升级到 v6 的全面指南,同时希望能够尽可能频繁地发布。

本指南中的示例将展示您可能在 v5 应用程序中构建某些内容的代码示例,然后是如何在 v6 中完成相同操作的示例。还将解释我们为什么进行此更改以及它将如何改善您的代码以及使用您应用程序的人的整体用户体验。

一般来说,这个过程是这样的

  1. 升级到 React v16.8 或更高版本
  2. 升级到 React Router v5.1
  3. 升级到 React Router v6

以下是每个步骤的详细分解,应该可以帮助您快速自信地迁移到 v6。

升级到 React v16.8

React Router v6 大量使用 React 钩子,因此您需要在尝试升级到 React Router v6 之前使用 React 16.8 或更高版本。好消息是 React Router v5 与 React >= 15 兼容,因此如果您使用的是 v5(或 v4),您应该能够升级 React 而不触碰任何路由代码。

升级到 React 16.8 后,您应该部署您的应用程序。然后您可以稍后再回来继续进行。

升级到 React Router v5.1

如果您先升级到 v5.1,那么切换到 React Router v6 会更容易。在 v5.1 中,我们发布了对 <Route children> 元素处理的增强功能,这将有助于顺利过渡到 v6。与其使用 <Route component><Route render> 属性,不如在所有地方都使用常规元素 <Route children>,并使用钩子来访问路由器的内部状态。

// v4 and v5 before 5.1
function User({ id }) {
  // ...
}

function App() {
  return (
    <Switch>
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route
        path="/users/:id"
        render={({ match }) => (
          <User id={match.params.id} />
        )}
      />
    </Switch>
  );
}

// v5.1 preferred style
function User() {
  let { id } = useParams();
  // ...
}

function App() {
  return (
    <Switch>
      <Route exact path="/">
        <Home />
      </Route>
      <Route path="/about">
        <About />
      </Route>
      {/* Can also use a named `children` prop */}
      <Route path="/users/:id" children={<User />} />
    </Switch>
  );
}

您可以在我们的博客上阅读有关 v5.1 的钩子 API 以及迁移到常规元素的理由的更多信息 我们的博客

一般来说,React Router v5.1(和 v6)更喜欢元素而不是组件(或“元素类型”)。这有几个原因,但我们将在讨论 v6 的 <Route> API 时进一步讨论。

当您使用常规 React 元素时,您可以显式传递属性。这有助于随着时间的推移提高代码的可读性和可维护性。如果您使用 <Route render> 来获取参数,您可以在路由组件中使用 useParams 来代替。

除了升级到 v5.1 之外,您还应该用钩子替换所有 withRouter 的用法。您还应该删除所有不在 <Switch> 内部的“浮动” <Route> 元素。同样,关于 v5.1 的博客文章解释了如何更详细地执行此操作。

总之,要从 v4/5 升级到 v5.1,您应该

  • 使用 <Route children> 而不是 <Route render> 和/或 <Route component> 属性
  • 使用 我们的钩子 API 来访问路由器状态,例如当前位置和参数
  • 用钩子替换所有 withRouter 的用法
  • useRouteMatch 替换所有不在 <Switch> 内部的 <Route>,或者将它们包装在 <Switch>

删除 <Switch> 内部的 <Redirect>

删除所有直接位于 <Switch> 内部的 <Redirect> 元素。

如果您想在初始渲染时重定向,您应该将重定向逻辑移动到您的服务器(我们 在这里写了更多关于这方面的内容)。

如果您想在客户端重定向,请将您的 <Redirect> 移动到 <Route render> 属性中。

// Change this:
<Switch>
  <Redirect from="about" to="about-us" />
</Switch>

// to this:
<Switch>
  <Route path="about" render={() => <Redirect to="about-us" />} />
</Switch>

不在 <Switch> 内部的普通 <Redirect> 元素可以保留。它们将在 v6 中变为 <Navigate> 元素。

重构自定义 <Route>

用常规 <Route> 替换 <Switch> 内部的任何不是普通 <Route> 元素的元素。这包括任何 <PrivateRoute> 风格的自定义组件。

您可以 在这里阅读有关此背后的理由的更多信息,包括一些关于如何在 v5 中使用 <Route render> 属性来实现相同效果的提示。

发布它!

同样,一旦您的应用程序升级到 v5.1,您应该对其进行测试和部署,并在准备好继续时重新开始本指南。

升级到 React Router v6

注意:这是迁移中最大的一步,可能需要花费最多时间和精力。

对于此步骤,您需要安装 React Router v6。如果您通过 npm 管理依赖项

$ npm install react-router-dom
# or, for a React Native app
$ npm install react-router-native

您还需要从您的 package.json 中删除 history 依赖项。history 库是 v6 的直接依赖项(而不是对等依赖项),因此您永远不会直接导入或使用它。相反,您将使用 useNavigate() 钩子进行所有导航(见下文)。

将所有 <Switch> 元素升级到 <Routes>

React Router v6 引入了一个 Routes 组件,它有点像 Switch,但功能更强大。Routes 相对于 Switch 的主要优势是

  • <Routes> 内部的所有 <Route><Link> 都是相对的。这会导致 <Route path><Link to> 中的代码更简洁、更可预测
  • 路由是根据最佳匹配来选择的,而不是按顺序遍历的。这避免了由于在您的 <Switch> 中稍后定义了不可达路由而导致的错误
  • 路由可以在一个地方嵌套,而不是分散在不同的组件中。在中小型应用程序中,这使您能够轻松地一次查看所有路由。在大型应用程序中,您仍然可以将路由嵌套在通过 React.lazy 动态加载的捆绑包中

为了使用 v6,您需要将所有 <Switch> 元素转换为 <Routes>。如果您已经完成了升级到 v5.1,那么您已经完成了一半。

首先,让我们谈谈 v6 中的相对路由和链接。

在 v5 中,您必须非常明确地说明您希望如何嵌套路由和链接。在这两种情况下,如果您想要嵌套路由和链接,您必须从父路由的 match.urlmatch.path 属性构建 <Route path><Link to> 属性。此外,如果您想嵌套路由,您必须将它们放在子路由的组件中。

// This is a React Router v5 app
import {
  BrowserRouter,
  Switch,
  Route,
  Link,
  useRouteMatch,
} from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/">
          <Home />
        </Route>
        <Route path="/users">
          <Users />
        </Route>
      </Switch>
    </BrowserRouter>
  );
}

function Users() {
  // In v5, nested routes are rendered by the child component, so
  // you have <Switch> elements all over your app for nested UI.
  // You build nested routes and links using match.url and match.path.
  let match = useRouteMatch();

  return (
    <div>
      <nav>
        <Link to={`${match.url}/me`}>My Profile</Link>
      </nav>

      <Switch>
        <Route path={`${match.path}/me`}>
          <OwnUserProfile />
        </Route>
        <Route path={`${match.path}/:id`}>
          <UserProfile />
        </Route>
      </Switch>
    </div>
  );
}

这是 v6 中的同一个应用程序

// This is a React Router v6 app
import {
  BrowserRouter,
  Routes,
  Route,
  Link,
} from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="users/*" element={<Users />} />
      </Routes>
    </BrowserRouter>
  );
}

function Users() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>

      <Routes>
        <Route path=":id" element={<UserProfile />} />
        <Route path="me" element={<OwnUserProfile />} />
      </Routes>
    </div>
  );
}

关于 v6,在这个例子中需要注意一些重要的事情

  • <Route path><Link to> 是相对的。这意味着它们会自动基于父路由的路径和 URL 进行构建,因此您不必手动插值 match.urlmatch.path
  • <Route exact> 不见了。相反,具有后代路由(在其他组件中定义)的路由在其路径中使用尾随 * 来指示它们深度匹配
  • 您可以按任何顺序放置路由,路由器将自动检测当前 URL 的最佳路由。这可以防止由于在 <Switch> 中手动将路由放置在错误的顺序而导致的错误

您可能还注意到,v5 应用程序中的所有 <Route children> 都更改为 v6 中的 <Route element>。假设您遵循了升级到 v5.1 的步骤,这应该与将您的路由元素从子位置移动到名为 element 的属性一样简单。

<Route element> 的优势

在关于升级到 v5.1 的部分,我们承诺会讨论使用常规元素而不是组件(或元素类型)进行渲染的优势。让我们暂时休息一下升级,现在就来谈谈这个话题。

首先,我们看到 React 本身在 <Suspense fallback={<Spinner />}> API 中率先采用了这种方法。fallback 属性接受一个 React 元素,而不是组件。这使您可以轻松地将您想要的任何属性从渲染它的组件传递到您的 <Spinner>

使用元素而不是组件意味着我们不必提供 passProps 样式的 API,这样您就可以将所需的属性传递给您的元素。例如,在基于组件的 API 中,没有好的方法将属性传递给 <Route path=":userId" component={Profile} /> 匹配时渲染的 <Profile> 元素。大多数采用这种方法的 React 库最终会使用类似 <Route component={Profile} passProps={{ animate: true }} /> 的 API,或者使用渲染道具或高阶组件。

此外,如果您没有注意到,在 v4 和 v5 中,Route 的渲染 API 变得相当庞大。它大致是这样的

// Ah, this is nice and simple!
<Route path=":userId" component={Profile} />

// But wait, how do I pass custom props to the <Profile> element??
// Hmm, maybe we can use a render prop in those situations?
<Route
  path=":userId"
  render={routeProps => (
    <Profile routeProps={routeProps} animate={true} />
  )}
/>

// Ok, now we have two ways to render something with a route. :/

// But wait, what if we want to render something when a route
// *doesn't* match the URL, like a Not Found page? Maybe we
// can use another render prop with slightly different semantics?
<Route
  path=":userId"
  children={({ match }) => (
    match ? (
      <Profile match={match} animate={true} />
    ) : (
      <NotFound />
    )
  )}
/>

// What if I want to get access to the route match, or I need
// to redirect deeper in the tree?
function DeepComponent(routeStuff) {
  // got routeStuff, phew!
}
export default withRouter(DeepComponent);

// Well hey, now at least we've covered all our use cases!
// ... *facepalm*

这种 API 膨胀至少部分原因是 React 没有提供任何方法让我们从 <Route> 获取信息到您的路由元素,因此我们不得不发明巧妙的方法来将路由数据 **和** 您自己的自定义属性传递到您的元素:component、渲染道具、passProps 高阶组件……直到 **hooks** 出现!

现在,上面的对话是这样的

// Ah, nice and simple API. And it's just like the <Suspense> API!
// Nothing more to learn here.
<Route path=":userId" element={<Profile />} />

// But wait, how do I pass custom props to the <Profile>
// element? Oh ya, it's just an element. Easy.
<Route path=":userId" element={<Profile animate={true} />} />

// Ok, but how do I access the router's data, like the URL params
// or the current location?
function Profile({ animate }) {
  let params = useParams();
  let location = useLocation();
}

// But what about components deep in the tree?
function DeepComponent() {
  // oh right, same as anywhere else
  let navigate = useNavigate();
}

// Aaaaaaaaand we're done here.

在 v6 中使用 element 属性的另一个重要原因是 <Route children> 用于嵌套路由。这是人们最喜欢的 v3 和 @reach/router 的功能之一,我们将在 v6 中将其带回来。将上一个示例中的代码再进一步,我们可以将所有 <Route> 元素提升到单个路由配置中

// This is a React Router v6 app
import {
  BrowserRouter,
  Routes,
  Route,
  Link,
  Outlet,
} from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="users" element={<Users />}>
          <Route path="me" element={<OwnUserProfile />} />
          <Route path=":id" element={<UserProfile />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

function Users() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>

      <Outlet />
    </div>
  );
}

当然,此步骤是可选的,但对于没有成千上万条路由的中小型应用程序来说,它非常有用。

请注意,<Route> 元素如何自然地嵌套在 <Routes> 元素中。嵌套路由通过添加到父路由的路径来构建它们的路径。这次我们不需要在 <Route path="users"> 上使用尾部 *,因为当路由在一个地方定义时,路由器能够看到您所有的嵌套路由。

您只需要在该路由的子代树中存在另一个 <Routes> 时才需要尾部 *。在这种情况下,子代 <Routes> 将匹配路径名中剩余的部分(有关实际情况,请参见上一个示例)。

当使用嵌套配置时,具有 children 的路由应渲染一个 <Outlet> 以渲染其子路由。这使得使用嵌套 UI 渲染布局变得容易。

关于 <Route path> 模式的说明

React Router v6 使用简化的路径格式。v6 中的 <Route path> 仅支持两种类型的占位符:动态 :id 样式参数和 * 通配符。* 通配符只能在路径的末尾使用,不能在中间使用。

以下所有都是 v6 中有效的路由路径

/groups
/groups/admin
/users/:id
/users/:id/messages
/files/*
/files/:id/*

以下 RegExp 样式的路由路径在 v6 中 **无效**

/users/:id?
/tweets/:id(\d+)
/files/*/cat.jpg
/files-*

我们在 v4 中添加了对 path-to-regexp 的依赖,以实现更高级的模式匹配。在 v6 中,我们使用更简单的语法,使我们能够以可预测的方式解析路径以进行排名。这也意味着我们可以停止依赖 path-to-regexp,这对捆绑包大小很有帮助。

如果您使用过 path-to-regexp 的任何更高级的语法,则需要将其删除并简化您的路由路径。如果您使用 RegExp 语法进行 URL 参数验证(例如,确保 id 都是数字字符),请注意,我们计划在 v6 中的某个时间点添加一些更高级的参数验证。现在,您需要将该逻辑移动到路由渲染的组件中,并在解析参数后让它分支其渲染的树。

如果您使用过 <Route sensitive>,则应将其移动到其包含的 <Routes caseSensitive> 属性中。<Routes> 元素中的所有路由要么区分大小写,要么不区分大小写。

需要注意的另一件事是,v6 中的所有路径匹配都忽略了 URL 中的尾部斜杠。实际上,<Route strict> 已被删除,在 v6 中没有效果。**这并不意味着您不能使用尾部斜杠,如果您需要的话。**您的应用程序可以决定是否使用尾部斜杠,您只是不能在 <Route path="edit"><Route path="edit/">客户端渲染两个不同的 UI。您仍然可以在这些 URL 上渲染两个不同的 UI(尽管我们不建议这样做),但您必须在服务器端进行。

在 v5 中,不以 / 开头的 <Link to> 值是模棱两可的;它取决于当前 URL 是什么。例如,如果当前 URL 是 /users,则 v5 <Link to="me"> 将渲染一个 <a href="/me">。但是,如果当前 URL 有一个尾部斜杠,例如 /users/,则相同的 <Link to="me"> 将渲染 <a href="/users/me">。这使得难以预测链接的行为,因此在 v5 中,我们建议您从根 URL(使用 match.url)构建链接,而不是使用相对 <Link to> 值。

React Router v6 修复了这种歧义。在 v6 中,<Link to="me"> 将始终渲染相同的 <a href>,无论当前 URL 是什么。

例如,渲染在 <Route path="users"> 中的 <Link to="me"> 将始终渲染指向 /users/me 的链接,无论当前 URL 是否有尾部斜杠。

当您想链接回“向上”到父路由时,请在您的 <Link to> 值中使用前导 .. 段,类似于您在 <a href> 中所做的那样。

function App() {
  return (
    <Routes>
      <Route path="users" element={<Users />}>
        <Route path=":id" element={<UserProfile />} />
      </Route>
    </Routes>
  );
}

function Users() {
  return (
    <div>
      <h2>
        {/* This links to /users - the current route */}
        <Link to=".">Users</Link>
      </h2>

      <ul>
        {users.map((user) => (
          <li>
            {/* This links to /users/:id - the child route */}
            <Link to={user.id}>{user.name}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

function UserProfile() {
  return (
    <div>
      <h2>
        {/* This links to /users - the parent route */}
        <Link to="..">All Users</Link>
      </h2>

      <h2>
        {/* This links to /users/:id - the current route */}
        <Link to=".">User Profile</Link>
      </h2>

      <h2>
        {/* This links to /users/mj - a "sibling" route */}
        <Link to="../mj">MJ</Link>
      </h2>
    </div>
  );
}

将当前 URL 视为文件系统上的目录路径,并将 <Link to> 视为 cd 命令行实用程序可能会有所帮助。

// If your routes look like this
<Route path="app">
  <Route path="dashboard">
    <Route path="stats" />
  </Route>
</Route>

// and the current URL is /app/dashboard (with or without
// a trailing slash)
<Link to="stats">               => <a href="/app/dashboard/stats">
<Link to="../stats">            => <a href="/app/stats">
<Link to="../../stats">         => <a href="/stats">
<Link to="../../../stats">      => <a href="/stats">

// On the command line, if the current directory is /app/dashboard
cd stats                        # pwd is /app/dashboard/stats
cd ../stats                     # pwd is /app/stats
cd ../../stats                  # pwd is /stats
cd ../../../stats               # pwd is /stats

注意:在匹配和创建相对路径时忽略尾部斜杠的决定并非我们团队轻易做出的。我们咨询了许多朋友和客户(他们也是我们的朋友!)关于此事。我们发现,我们大多数人甚至不了解普通 HTML 相对链接是如何处理尾部斜杠的。大多数人猜测它的工作方式类似于命令行上的 cd(事实并非如此)。此外,HTML 相对链接没有嵌套路由的概念,它们只在 URL 上工作,因此我们不得不在这里开辟自己的道路。@reach/router 设定了这个先例,并且在过去几年中运行良好。

除了忽略当前 URL 中的尾部斜杠之外,重要的是要注意,当您的 <Route path> 匹配 URL 的多个段时,<Link to=".."> 不会始终像 <a href=".."> 一样工作。它不会只删除 URL 的一个段,**而是会根据父路由的路径进行解析,本质上会删除该路由指定的所有路径段**。

function App() {
  return (
    <Routes>
      <Route path="users">
        <Route
          path=":id/messages"
          element={
            // This links to /users
            <Link to=".." />
          }
        />
      </Route>
    </Routes>
  );
}

这可能看起来是一个奇怪的选择,让 .. 在路由而不是 URL 段上运行,但这在处理 * 路由时是一个 **巨大** 的帮助,其中可能会有不确定的数量的段与 * 匹配。在这些情况下,您的 <Link to> 值中的单个 .. 段可以基本上删除与 * 匹配的任何内容,这使您可以在 * 路由中创建更可预测的链接。

function App() {
  return (
    <Routes>
      <Route path=":userId">
        <Route path="messages" element={<UserMessages />} />
        <Route
          path="files/*"
          element={
            // This links to /:userId/messages, no matter
            // how many segments were matched by the *
            <Link to="../messages" />
          }
        />
      </Route>
    </Routes>
  );
}

v6 中的 Link 组件接受 state 作为单独的属性,而不是将其作为传递给 to 的对象的一部分接收,因此如果您使用 state,则需要更新您的 Link 组件

import { Link } from "react-router-dom";

// Change this:
<Link to={{ pathname: "/home", state: state }} />

// to this:
<Link to="/home" state={state} />

状态值仍然使用 useLocation() 在链接的组件中检索

function Home() {
  const location = useLocation();
  const state = location.state;
  return <div>Home</div>;
}

使用 useRoutes 而不是 react-router-config

v5 的 react-router-config 包中的所有功能都已在 v6 中移至核心。如果您更喜欢/需要将路由定义为 JavaScript 对象而不是使用 React 元素,那么您一定会喜欢它。

function App() {
  let element = useRoutes([
    // These are the same as the props you provide to <Route>
    { path: "/", element: <Home /> },
    { path: "dashboard", element: <Dashboard /> },
    {
      path: "invoices",
      element: <Invoices />,
      // Nested routes use a children property, which is also
      // the same as <Route>
      children: [
        { path: ":id", element: <Invoice /> },
        { path: "sent", element: <SentInvoices /> },
      ],
    },
    // Not found routes work as you'd expect
    { path: "*", element: <NotFound /> },
  ]);

  // The returned element will render the entire element
  // hierarchy with all the appropriate context it needs
  return element;
}

以这种方式定义的路由遵循与 <Routes> 相同的所有语义。实际上,<Routes> 实际上只是 useRoutes 的包装器。

我们鼓励您尝试使用 <Routes>useRoutes,并自行决定您更喜欢使用哪一个。老实说,我们喜欢并使用它们。

如果您已经围绕服务器端的数据获取和渲染编写了一些自己的逻辑,我们还提供了一个低级别的 matchRoutes 函数,类似于我们在 react-router-config 中提供的函数。

使用 useNavigate 而不是 useHistory

React Router v6 引入了一个新的导航 API,它与 <Link> 同义,并提供与支持悬念的应用程序更好的兼容性。我们根据您的风格和需求提供了该 API 的命令式和声明式版本。

// This is a React Router v5 app
import { useHistory } from "react-router-dom";

function App() {
  let history = useHistory();
  function handleClick() {
    history.push("/home");
  }
  return (
    <div>
      <button onClick={handleClick}>go home</button>
    </div>
  );
}

在 v6 中,此应用程序应改写为使用 navigate API。大多数情况下,这意味着将 useHistory 更改为 useNavigate,并将 history.pushhistory.replace 调用点更改。

// This is a React Router v6 app
import { useNavigate } from "react-router-dom";

function App() {
  let navigate = useNavigate();
  function handleClick() {
    navigate("/home");
  }
  return (
    <div>
      <button onClick={handleClick}>go home</button>
    </div>
  );
}

如果您需要替换当前位置而不是将新位置推送到历史堆栈中,请使用 navigate(to, { replace: true })。如果您需要状态,请使用 navigate(to, { state })。您可以将 navigate 的第一个参数视为您的 <Link to>,并将其他参数视为 replacestate 属性。

如果您更喜欢使用声明式 API 进行导航(类似于 v5 的 Redirect 组件),v6 提供了一个 Navigate 组件。像这样使用它

import { Navigate } from "react-router-dom";

function App() {
  return <Navigate to="/home" replace state={state} />;
}

注意:请注意,v5 <Redirect /> 默认使用 replace 逻辑(您可以通过 push 属性更改它),另一方面,v6 <Navigate /> 默认使用 push 逻辑,您可以通过 replace 属性更改它。

// Change this:
<Redirect to="about" />
<Redirect to="home" push />

// to this:
<Navigate to="about" replace />
<Navigate to="home" />

如果您当前使用 useHistory 中的 gogoBackgoForward 来向前和向后导航,您也应该将它们替换为 navigate,并使用一个数字参数来指示在历史堆栈中移动指针的位置。例如,以下是一些使用 v5 的 useHistory 钩子的代码

// This is a React Router v5 app
import { useHistory } from "react-router-dom";

function App() {
  const { go, goBack, goForward } = useHistory();

  return (
    <>
      <button onClick={() => go(-2)}>
        Go 2 pages back
      </button>
      <button onClick={goBack}>Go back</button>
      <button onClick={goForward}>Go forward</button>
      <button onClick={() => go(2)}>
        Go 2 pages forward
      </button>
    </>
  );
}

以下是使用 v6 的等效应用程序

// This is a React Router v6 app
import { useNavigate } from "react-router-dom";

function App() {
  const navigate = useNavigate();

  return (
    <>
      <button onClick={() => navigate(-2)}>
        Go 2 pages back
      </button>
      <button onClick={() => navigate(-1)}>Go back</button>
      <button onClick={() => navigate(1)}>
        Go forward
      </button>
      <button onClick={() => navigate(2)}>
        Go 2 pages forward
      </button>
    </>
  );
}

同样,我们从直接使用 history API 转向使用 navigate API 的主要原因之一是为 React 悬念提供更好的兼容性。React Router v6 在您的组件层次结构的根部使用 useNavigation 钩子。这使我们能够在用户交互需要中断挂起的路由导航时提供更流畅的体验,例如,当用户在先前单击的链接仍在加载时单击指向另一个路由的链接时。navigate API 了解内部挂起的导航状态,并将执行 REPLACE 而不是 PUSH 到历史堆栈,因此用户最终不会在他们的历史记录中拥有从未真正加载过的页面。

注意:v5 中的 <Redirect> 元素不再作为您路由配置的一部分(在 <Routes> 中)受支持。这是由于 React 中即将发生的更改,这些更改使得在初始渲染期间更改路由的状态变得不安全。如果您需要立即重定向,您可以 a) 在您的服务器上执行此操作(可能是最好的解决方案)或 b) 在您的路由组件中渲染一个 <Navigate> 元素。但是,请认识到导航将在 useEffect 中发生。

除了悬念兼容性之外,navigateLink 一样,支持相对导航。例如

// assuming we are at `/stuff`
function SomeForm() {
  let navigate = useNavigate();
  return (
    <form
      onSubmit={async (event) => {
        let newRecord = await saveDataFromForm(
          event.target
        );
        // you can build up the URL yourself
        navigate(`/stuff/${newRecord.id}`);
        // or navigate relative, just like Link
        navigate(`${newRecord.id}`);
      }}
    >
      {/* ... */}
    </form>
  );
}

<Link> 不再支持 component 属性来覆盖返回的锚点标签。这有几个原因。

首先,<Link> 几乎总是应该渲染一个 <a>。如果你的没有,很有可能你的应用程序存在一些严重的无障碍性和可用性问题,这可不是什么好事。浏览器为我们提供了许多关于 <a> 的良好可用性功能,我们希望你的用户能够免费获得这些功能!

话虽如此,也许你的应用程序使用了一个 CSS-in-JS 库,或者也许你已经在你的设计系统中拥有一个自定义的、花哨的链接组件,你希望渲染它。component 属性可能在钩子出现之前的世界中运行良好,但现在你可以使用我们的一些钩子创建你自己的无障碍 Link 组件。

import { FancyPantsLink } from "@fancy-pants/design-system";
import {
  useHref,
  useLinkClickHandler,
} from "react-router-dom";

const Link = React.forwardRef(
  (
    {
      onClick,
      replace = false,
      state,
      target,
      to,
      ...rest
    },
    ref
  ) => {
    let href = useHref(to);
    let handleClick = useLinkClickHandler(to, {
      replace,
      state,
      target,
    });

    return (
      <FancyPantsLink
        {...rest}
        href={href}
        onClick={(event) => {
          onClick?.(event);
          if (!event.defaultPrevented) {
            handleClick(event);
          }
        }}
        ref={ref}
        target={target}
      />
    );
  }
);

如果你使用的是 react-router-native,我们提供了 useLinkPressHandler,它的工作原理基本相同。只需在你的 LinkonPress 处理程序中调用该钩子返回的函数,你就可以开始了。

这只是对属性的简单重命名,以更好地与 React 生态系统中其他库的通用做法保持一致。

v6.0.0-beta.3 开始,activeClassNameactiveStyle 属性已从 NavLinkProps 中移除。相反,你可以将一个函数传递给 styleclassName,它将允许你根据组件的活动状态自定义内联样式或类字符串。

<NavLink
  to="/messages"
- style={{ color: 'blue' }}
- activeStyle={{ color: 'green' }}
+ style={({ isActive }) => ({ color: isActive ? 'green' : 'blue' })}
>
  Messages
</NavLink>
<NavLink
  to="/messages"
- className="nav-link"
- activeClassName="activated"
+ className={({ isActive }) => "nav-link" + (isActive ? " activated" : "")}
>
  Messages
</NavLink>

如果你希望保留 v5 属性,你可以创建你自己的 <NavLink /> 作为包装组件,以获得更平滑的升级路径。

import * as React from "react";
import { NavLink as BaseNavLink } from "react-router-dom";

const NavLink = React.forwardRef(
  ({ activeClassName, activeStyle, ...props }, ref) => {
    return (
      <BaseNavLink
        ref={ref}
        {...props}
        className={({ isActive }) =>
          [
            props.className,
            isActive ? activeClassName : null,
          ]
            .filter(Boolean)
            .join(" ")
        }
        style={({ isActive }) => ({
          ...props.style,
          ...(isActive ? activeStyle : null),
        })}
      />
    );
  }
);

react-router-dom/server 获取 StaticRouter

StaticRouter 组件已移至一个新的包:react-router-dom/server

// change
import { StaticRouter } from "react-router-dom";
// to
import { StaticRouter } from "react-router-dom/server";

此更改是为了更紧密地遵循 react-dom 包所建立的约定,并帮助用户更好地理解 <StaticRouter> 的用途以及何时应该使用它(在服务器上)。

useRouteMatch 替换为 useMatch

useMatch 与 v5 的 useRouteMatch 非常相似,但有一些关键区别

  • 它使用我们新的 路径模式匹配算法
  • 模式参数现在是必需的
  • 不再接受模式数组
  • 当将模式作为对象传递时,一些选项已被重命名以更好地与 v6 中的其他 API 保持一致
    • useRouteMatch({ strict }) 现在是 useMatch({ end })
    • useRouteMatch({ sensitive }) 现在是 useMatch({ caseSensitive })
  • 它返回一个具有不同形状的匹配对象

要查看新 useMatch 钩子和其类型声明的精确 API,请查看我们的 API 参考

更改传递给 matchPath 的参数顺序。更改 pathPattern 选项。

从版本 6 开始,传递给 matchPath 函数的参数顺序已更改。模式选项也已更改。

  • 第一个参数是 pathPattern 对象,然后是 pathname
  • pathPattern 不再包含 exactstrict 选项。已添加新的 caseSensitiveend 选项。

请按如下方式重构它

之前

// This is a React Router v5 app
import { matchPath } from "react-router-dom";

const match = matchPath("/users/123", {
  path: "/users/:id",
  exact: true, // Optional, defaults to false
  strict: false, // Optional, defaults to false
});

之后

// This is a React Router v6 app
import { matchPath } from "react-router-dom";

const match = matchPath(
  {
    path: "/users/:id",
    caseSensitive: false, // Optional, `true` == static parts of `path` should match case
    end: true, // Optional, `true` == pattern should match the entire URL pathname
  },
  "/users/123"
);

<Prompt> 目前不受支持

v5 中的 <Prompt>(以及 v6 beta 版中的 usePromptuseBlocker)未包含在 v6 的当前发布版本中。我们决定与其花费更多时间来确定一个尚未完全成熟的功能,不如发布我们现有的功能。我们一定会尽快在 v6 中添加回这个功能,但不会在 6.x 的第一个稳定版本中添加。

我们已经为 useBlockerunstable_usePrompt 添加了实现,你可以使用它们来代替 <Prompt>

我们错过了什么?

尽管我们尽力做到全面,但我们很可能遗漏了一些东西。如果你遵循此升级指南并发现情况并非如此,请告诉我们。我们很乐意帮助你弄清楚如何处理你的 v5 代码,以便能够升级并利用 v6 中的所有酷炫功能。

祝你好运 🤘