主線
分支
主線 (6.23.1)開發
版本
6.23.1v4/5.xv3.x
從 v5 升級
在此頁面

從 v5 升級

後向相容性套件

不僅無需一次升級和更新所有程式碼(這相當困難,而且容易產生錯誤),向下相容性套件還能讓你一次升級一個元件、一個掛勾和一個路由,並同時執行 v5 和 v6。所有你未曾變更的程式碼,依然執行與過去完全相同的程式碼。一旦所有元件都明確使用 v6 API,你的應用程式便不再需要相容性套件,並執行於 v6。官方指南可以在 此處 找到。

我們建議對具有多個路由的應用程式,使用向下相容性套件進行升級。否則,我們仍希望這份指南能協助你一次完成升級!

簡介

React Router 第六版新增了多項強大的新功能,以及與 React 最新版本的相容性改善。它同時從第五版引入了幾個不支援的變更。這份文件提供一份完整的指南,說明如何將你的 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 的 hook API 以及轉移到常規元素的背後原因

一般來說,React Router v5.1(和 v6)偏好元素而非組件(或「元素類型」)。有幾個原因導致如此,不過當我們討論 v6 的 <Route> API 時會進一步討論。

使用一般 React 元素時,您可以明確傳遞 props。隨著時間的推移,這有助於程式碼的可讀性和維護性。如果您使用 <Route render> 來掌握 params,您只要在您的 Route 組件中使用 useParams 就行了。

隨著升級至 v5.1,您應該用 hooks 取代所有 withRouter 使用方式。您還應該移除任何不在 <Switch> 中的「浮動」<Route>。同樣地,關於 v5.1 的部落格文章 更詳細地說明如何進行此步驟。

總而言之,要從 v4/5 升級至 v5.1,您應該

  • 使用 <Route children> 取代 <Route render> 和/或 <Route component> props
  • 使用 我們的 hooks API 存取路由狀態,例如目前的位置和參數
  • 使用 hook 取代所有 withRouter 用法
  • 使用 useRouteMatch 取代任何不在 <Switch> 中的<Route>,或將其包裝在 <Switch>

移除 <Switch> 中的 <Redirect>

移除直接位於 <Switch> 中的任何 <Redirect> 元素。

如果您想要在初始呈現時進行重新導向,您應將重新導向邏輯移至伺服器(我們 在此處撰寫更多有關這方面內容)。

如果您想要在用戶端進行重新導向,請將您的 <Redirect> 移至 <Route render> props。

// 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> props 來取得相同效果的秘訣。

完成!

再次提醒,一旦您的應用程式升級至 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 的主要优势是

  • 所有 <Route><Link><Routes> 中都是相关的。这会在 <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 的 prop 一樣簡單。

<Route element> 的優點

在關於升級到 v5.1 的章節中,我們承諾會討論使用一般元素而非元件(或元素類型)進行呈現的優點。讓我們暫時中斷升級,現在就來談談這個話題。

首先,我們看到 React 本身使用 <Suspense fallback={<Spinner />}> API 引領此風潮。fallback prop 會使用 React 元素,而非元件。這樣就能讓您輕鬆地從呈現它的元件將任何所需的 prop 傳遞給 <Spinner>

使用元素而非元件表示我們不必提供 passProps 風格的 API,以便讓您取得傳遞給元素的所需 prop。舉例來說,在基於元件的 API 中,沒有好的方法可以將 prop 傳遞給在 <Route path=":userId" component={Profile} /> 相符時呈現的 <Profile> 元素。採用這種方法的大部分 React 函式庫最終會使用 <Route component={Profile} passProps={{ animate: true }} /> 這樣的 API,或使用呈現 prop 或高階元件。

此外,如果您沒有注意到,在 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> 的資訊並傳遞給您的路由元素,因此我們必須發明巧妙的方式讓路由資料以及您自己的自訂 prop 傳遞到您的元素:component、呈現 prop、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 prop 的另一個重要原因是,<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> 只支援 2 種類型的佔位符:動態的 :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/"> 呈現兩個不同的使用者介面 於客戶端側。你仍然可以在這些 URL 呈現兩個不同的使用者介面(雖然我們不建議這麼做),但是你必須在伺服器端執行此操作。

在 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 元件以獨立 prop 的方式接受 state,而非接收傳遞給 to 的物件中的一部分,因此若您的 Link 元件有使用 state,將需要進行更新。

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> 同義,也提供更佳的懸念式 app 相容性。我們包含命令式和宣告式版本,視您的風格和需要而定。

// 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 中,此 app 應改寫為使用 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 prop。

若您偏好使用宣告式 API 進行導覽(類似 v5 的 Redirect 元件),v6 提供一個 Navigate 元件。使用方式如下

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

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

注意:請注意 v5 的 <Redirect /> 預設使用 replace 邏輯(您可透過 push prop 進行變更),另一方面,v6 的 <Navigate /> 預設使用 push 邏輯,且您可透過 replace prop 進行變更。

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

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

如果您目前使用 useHistory 中的 gogoBackgoForward 進行前後移動,您也應該將這些替換為 navigate,其中數值參數表示將指標在歷史堆疊中移動到的位置。例如,以下是使用 v5 的 useHistory hook 之一些程式碼

// 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 Suspense 更好的相容性。React Router v6 在元件階層的根目錄使用 useNavigation hook。這樣一來,當使用者互動需要中斷待處理的路線導航時,例如在先前按一下的連結仍在載入時按一下前往另一個路線的連結,我們可以提供更順暢的體驗。navigate API 會知道內部的待處理導航狀態,並且會在歷史堆疊中執行 REPLACE 而不是 PUSH,因此使用者不會在他們的歷史記錄中看到從來沒有實際載入的頁面。

注意:<Redirect> 元素已不再受 v5 支援,作為您的路線組態的一部分(位於 <Routes> 內)。這是因為 React 中的未來變更,使得在初始呈現期間變更路由狀態會變得不安全。如果您需要立即重新導向,您可能 a) 在伺服器上執行(可能是最佳解法)或 b) 在您的路線元件中呈現 <Navigate> 元素。不過,請注意導航將會在 useEffect 中發生。

除了 Suspense 相容性之外,navigate(例如 Link)支援相關導航。例如

// 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 prop。有幾個原因。

首先,<Link> 幾乎總是應該呈現 <a>。如果您的元件沒有呈現,您的應用程式很有可能有一些嚴重的無障礙性和可用性問題,這可不好。瀏覽器透過 <a> 為我們提供了許多不錯的可用性功能,而我們希望您的使用者可以免費使用這些功能!

話雖如此,您的應用程式可能使用 CSS-in-JS 程式庫,或者您的設計系統中可能已經有一個自訂的精緻連結元件,您希望改為呈現那個元件。在 hook 之前,component prop 可能已經運作得夠好,但現在您只要使用一些 hook,就可以建立您自己的無障礙 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 處理函式中呼叫那個 hook 回傳的函式,就大功告成了。

這只是簡單地重新命名道具,以更好地與 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 物件,接著是路徑名稱
  • 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>

<Prompt> 從 v5(連同 v6 beta 版的 usePromptuseBlocker)不包含在 v6 目前發布的版本中。我們決定寧可使用現有的功能,也不願花更多時間來確定還未完全成熟的功能。我們絕對會在不久的將來盡快將其重新加入 v6,但不會用於 6.x 的第一個穩定版本。

我們已經為 useBlockerunstable_usePrompt 加入實作,你可以用它們來取代 <Prompt>

我們錯過什麼?

儘管我們盡最大努力做到周全,但我們很可能錯過了一些東西。如果你按照此升級指南操作,發現情況確實如此,請告訴我們。我們很樂意説明你找出如何處理你的 v5 程式碼,以升級並利用 v6 中所有酷炫的功能。

祝你好運 🤘