main
分支
main (6.23.1)dev
版本
6.23.1v4/5.xv3.x
常見問題解答
在此頁面

常見問題

以下是人們常問 React Router v6 的一些問題。您也可以在 範例 中找到您要找的答案。

withRouter 發生什麼事了?我需要它!

這個問題通常源於您使用 React 類別元件,這類元件不支援 hooks。在 React Router v6,我們完全採用 hooks,並使用它們來共用所有路由的內部狀態。但这并不表示您无法使用路由。假设您实际上可以使用 hooks(您使用的是 React 16.8+),您只需要一个包装器。

import {
  useLocation,
  useNavigate,
  useParams,
} from "react-router-dom";

function withRouter(Component) {
  function ComponentWithRouterProp(props) {
    let location = useLocation();
    let navigate = useNavigate();
    let params = useParams();
    return (
      <Component
        {...props}
        router={{ location, navigate, params }}
      />
    );
  }

  return ComponentWithRouterProp;
}

為什麼 <Route> 有一個 element prop,而不是 rendercomponent

在 React Router v6 中,我們從使用 v5 的 <Route component><Route render> API 轉換為 <Route element>。為什麼要這樣做?

首先,我們看到 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,或使用渲染道具或高階元件。

此外,第 5 版的 Route 渲染 API 相當龐大。在開發第 4 版和第 5 版的過程中,討論內容大概如下

// 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> 至您的路由元素的資訊,因此我們必須發明聰明的方法,以便將路線資料 您自己的自訂道具傳遞給元素:組件、渲染道具、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.

第 6 版中使用 element 道具的另一個重要原因,在於 <Route children> 保留用於巢狀路由。在關於第 6 版的 入門指南 中,您可以進一步瞭解這一點。

如何在 react-router v6 中新增找不到相符路線 (404) 的路線?

在第 4 版中,我們會直接略過路線的 path 道具。在第 5 版中,我們會使用 Route 包住我們的 404 元素,並使用 path="*"。在第 6 版中,請使用 path="*",並且將 404 元素傳遞進新的 element 道具中,而非包住它

<Route path="*" element={<NoMatch />} />

<Route> 沒有渲染?我該如何撰寫組成?

在第 5 版中,<Route> 組件只是一個正規組件,就像 if 敘述,在網址符合路徑時渲染。在第 6 版中,<Route> 元素實際上不會渲染,它只是用於設定。

在第 5 版中,由於路徑只是組件,因此當路徑為 "/my-route" 時,會渲染 MyRoute

let App = () => (
  <div>
    <MyRoute />
  </div>
);

let MyRoute = ({ element, ...rest }) => {
  return (
    <Route path="/my-route" children={<p>Hello!</p>} />
  );
};

然而,在第 6 版中,<Route> 僅用於其道具,因此以下程式碼永遠不會渲染 <p>Hello!</p>,因為 <MyRoute> 沒有 <Routes> 能看見的路徑

let App = () => (
  <Routes>
    <MyRoute />
  </Routes>
);

let MyRoute = () => {
  // won't ever render because the path is down here
  return (
    <Route path="/my-route" children={<p>Hello!</p>} />
  );
};

您可以透過以下方式取得相同的行為

  • 僅在 <Routes> 內渲染 <Route> 元素
  • 將組成移至 element 道具中
let App = () => (
  <div>
    <Routes>
      <Route path="/my-route" element={<MyRoute />} />
    </Routes>
  </div>
);

let MyRoute = () => {
  return <p>Hello!</p>;
};

將一個完整的巢狀路由組態靜態地存在於<Routes>中,將會啟用v6.x中的許多功能,因此我們鼓勵您將路由放入頂層組態中。如果您真的喜歡不依賴任何其他元件與 URL 相符的元件概念,您可以使用這個元件來做出與 v5「Route」相似的行為

function MatchPath({ path, Comp }) {
  let match = useMatch(path);
  return match ? <Comp {...match} /> : null;
}

// Will match anywhere w/o needing to be in a `<Routes>`
<MatchPath path="/accounts/:id" Comp={Account} />;

如何將路由巢狀深入樹狀結構中?

在 v5 中您可以隨意呈現<Route>或<Switch>。您仍然可以執行相同的事情,但是您需要使用<Routes>(沒有「s」的<Route>將無法運作)。我們稱這些為「後代<Routes>」

它在 v5 中看起來可能像這樣

// somewhere up the tree
<Switch>
  <Route path="/users" component={Users} />
</Switch>;

// and now deeper in the tree
function Users() {
  return (
    <div>
      <h1>Users</h1>
      <Switch>
        <Route path="/users/account" component={Account} />
      </Switch>
    </div>
  );
}

在 v6 中它幾乎是一樣的

  • 請注意祖先路由中的*,即使它沒有子節點,也能讓它與較深入的 URL 做匹配
  • 您不再需要知道完整的子路由路徑,現在您可以使用相對路由
// somewhere up the tree
<Routes>
  <Route path="/users/*" element={<Users />} />
</Routes>;

// and now deeper in the tree
function Users() {
  return (
    <div>
      <h1>Users</h1>
      <Routes>
        <Route path="account" element={<Account />} />
      </Routes>
    </div>
  );
}

如果您在 v5 中有一個「浮動路由」(未包覆在<Switch>中),只需將其包覆在<Routes>中即可

// v5
<Route path="/contact" component={Contact} />

// v6
<Routes>
  <Route path="contact" element={<Contact />} />
</Routes>

正規表示法路由路徑發生了什麼事?

移除正規表示法路由路徑有兩個原因

  1. 路由中的正規表示法路徑為 v6 的已排序路由匹配提出了很多問題。您如何對正規表示法進行排序?

  2. 我們得以移除一個相依關係(path-to-regexp),並大幅降低發送到用戶瀏覽器的套件權重。如果將它添加回去,它將會佔 React 路由器頁面權重的三分之一!

在查看許多使用案例之後,我們發現我們仍然可以在不直接支援正規表示法路徑的情況下滿足需求,所以我們做出權衡以顯著減少套件大小,並避免正規表示法路由排序周圍的懸而未決問題。

大多數正規表示法路由每次只關注一個 URL 區段,並執行下列其中一項

  1. 匹配多個靜態值
  2. 以某種方式驗證參數(是數字、不是數字等)

匹配一般靜態值

我們看過一個非常常見的路由是匹配多個語言代碼的正規表示法

function App() {
  return (
    <Switch>
      <Route path={/(en|es|fr)/} component={Lang} />
    </Switch>
  );
}

function Lang({ params }) {
  let lang = params[0];
  let translations = I81n[lang];
  // ...
}

這些實際上都是靜態路徑,因此在 v6 中您可以建立三個路由,並將代碼直接傳遞給元件。如果您有許多代碼,請建立一個陣列並將其對應至路由以避免重複。

function App() {
  return (
    <Routes>
      <Route path="en" element={<Lang lang="en" />} />
      <Route path="es" element={<Lang lang="es" />} />
      <Route path="fr" element={<Lang lang="fr" />} />
    </Routes>
  );
}

function Lang({ lang }) {
  let translations = I81n[lang];
  // ...
}

執行某種參數驗證

另一個常見情況是確保參數為整數。

function App() {
  return (
    <Switch>
      <Route path={/users\/(\d+)/} component={User} />
    </Switch>
  );
}

function User({ params }) {
  let id = params[0];
  // ...
}

在這種情況下,您必須在匹配元件內使用正規表示法親自執行一些工作

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

function ValidateUser() {
  let params = useParams();
  let userId = params.id.match(/\d+/);
  if (!userId) {
    return <NotFound />;
  }
  return <User id={params.userId} />;
}

function User(props) {
  let id = props.id;
  // ...
}

在 v5 中,如果正規表示法不符,<Switch>將會持續嘗試匹配下一個路由

function App() {
  return (
    <Switch>
      <Route path={/users\/(\d+)/} component={User} />
      <Route path="/users/new" exact component={NewUser} />
      <Route
        path="/users/inactive"
        exact
        component={InactiveUsers}
      />
      <Route path="/users/*" component={NotFound} />
    </Switch>
  );
}

看到此範例,你可能會擔心在 v6 版本中,你的其他路由不會在 URL 中呈現,因為 :userId 路由可能首先比對。但是,感謝路由排名,這並非這種情況。「new」和「inactive」路由排名會更高,因此會於各自的 URL 上呈現

function App() {
  return (
    <Routes>
      <Route path="/users/:id" element={<ValidateUser />} />
      <Route path="/users/new" element={<NewUser />} />
      <Route
        path="/users/inactive"
        element={<InactiveUsers />}
      />
    </Routes>
  );
}

事實上,如果你的路由沒有按照「完全正確」的順序排列,v5 版本會出現各種問題。V6 完全消除了此問題。

Remix 使用者

如果你使用的是 Remix,你可以透過將這項工作移到你的載入器中,將適當的 40x 回應傳送至瀏覽器。這也會降低傳送至使用者的瀏覽器套件大小,因為載入器僅在伺服器上執行。

import { useLoaderData } from "remix";

export async function loader({ params }) {
  if (!params.id.match(/\d+/)) {
    throw new Response("", { status: 400 });
  }

  let user = await fakeDb.user.find({
    where: { id: params.id },
  });
  if (!user) {
    throw new Response("", { status: 404 });
  }

  return user;
}

function User() {
  let user = useLoaderData();
  // ...
}

Remix 會呈現最近的 擷取邊界,而不是呈現你的元件。