分支
main (6.23.1)dev
版本
6.23.1v4/5.xv3.x
主要概念
此頁面中

主要概念

此文件需更新 6.4 資料 API

此文件深入探討 React Router 中路由背後的核心概念。內容很長,如果你想找尋較為實用的指南,可以看看我們的快速入門指南

你可能想知道 React Router 究竟做了什麼?它如何協助你建立 App?究竟什麼是路由

如果你曾對其中任何一個問題抱持疑問,或是你只想深入了解路由的基本概念,那麼你來對地方了。這份文件詳細解釋 React Router 中路由背後的所有核心概念。

請不要讓這份文件讓你不知所措!對於日常使用,React Router 非常簡單。你不需要深入瞭解就能使用它。

React Router 唔只是將 URL 配對至函式或組件:它會建立一個完整的使用者介面來對應 URL,因此它可能包含比你習慣更多的概念。我們將詳細說明 React Router 的三個主要任務

  1. 訂閱和操控 歷史記錄堆疊
  2. 網址 與你的 路徑 配對
  3. 根據 路由比對 渲染巢狀 UI

定義

首先,一些定義!在前後端框架中有很多關於路由的不同概念。有時,一個單字在某個脈絡中可能會與在其他脈絡中具有不同的意思。

以下是一些我們在談論 React Router 時經常使用的字詞。本指南的後續部分將更詳細地探討每個字詞。

  • 網址 - 地址列中的網址。許多人會互換使用「網址」和「路由」這兩個術語,但這不是 React Router 中的路由,而只是一個網址。

  • 位置 - 這是基於內建瀏覽器 window.location 物件的 React Router 特有物件。它代表「使用者的所在位置」。它大部分是網址的物件表示形式,但比那略為多一點。

  • 位置狀態 - 與 位置 一起保留但未編碼在 網址 中的值。很像雜湊或搜尋參數(編碼在網址中的資料),但隱藏地儲存在瀏覽器的記憶體中。

  • 歷史記錄堆疊 - 當使用者瀏覽時,瀏覽器會在堆疊中追蹤每個 位置。如果你在瀏覽器中按住返回按鈕,你可以看到瀏覽器的歷史記錄堆疊就在那裡。

  • 用戶端路由 (CSR) - 一般 HTML 文件可以連結到其他文件,而瀏覽器本身會處理 歷史記錄堆疊。用戶端路由讓開發人員可以操控瀏覽器歷史記錄堆疊,而無需對伺服器提出文件要求。

  • 歷史記錄 - React Router 可以用來訂閱 網址 變更的物件,並提供 API 用於以程式化方式操控瀏覽器 歷史記錄堆疊

  • 歷史記錄動作 - POPPUSHREPLACE 之一。使用者可以因以下三個原因之一到達 網址:將新項目加入歷史記錄堆疊時會產生推入動作(通常是點選連結或程式設計師強制導覽)。取代動作類似,只不過它會取代堆疊中的目前項目,而不是推入新的項目。最後,當使用者在瀏覽器工具列中點選返回或前進按鈕時會發生彈出動作。

  • 區段 - 網址路徑模式/ 字元之間的部分。例如,"/users/123" 有兩個區段。

  • 路徑模式 - 這些看似網址,但有些特殊字元用於將網址與路由相符,例如動態片段 ("/users/:userId") 或星號片段 ("/docs/*")。它們並非網址,它們是 React Router 將相符的模式。

  • 動態片段 - 路徑模式的區段為動態,表示可以相符區段中的任何值。例如模式 /users/:userId 將相符網址,例如 /users/123

  • 網址參數 - 從符合動態片段的網址分析而得的值。

  • 路由 - 有狀態、頂層的元件,它讓所有其他元件和掛勾都能運作。

  • 路由組態 - 路由物件樹,將根據目前的所在位置排序和相符 (包含巢狀),建立路由相符分支。

  • 路由 - 物件或路由元素,形狀通常為「{ 路徑, 元素 }」或「<Route 路徑 元素>」。「路徑」是路徑模式。當路徑模式相符目前的網址時,元素將被呈現。

  • 路由元素 - 或「<Route>」。它的屬性會被讀取,由「<Routes>」建立路由,否則不會進行任何動作。

  • 巢狀路由 - 由於路由可以有子代,且每個路由會定義網址的一部分透過片段,因此,單一網址可以在樹狀結構中相符多個路由的巢狀「分支」。這能透過出口相對連結等功能自動建立排版巢狀。

  • 相對連結 - 不以「/」開頭的連結會繼承它在其中被呈現的最接近路由。這能更輕鬆連結到更深層的網址,而不用知道完整路徑並建置它。

  • 相符 - 當路由相符網址時,會儲存資訊的物件,例如相符的網址參數和路徑名稱。

  • 相符 - 一組相符目前所在位置的路由 (或路由組態分支)。此結構能建立巢狀路由

  • 父路由 - 含有子路由的路由。

  • 出口 - 元件,在相符中呈現下一個相符。

  • 索引路由 - 沒有路徑的子路由,在父路由的出口中,在父路由的網址中呈現。

  • 版面路由 - 沒有路徑的父路由,專門用於在特定版面內群組子路由。

歷史記錄與地點

在 React Router 執行任何動作之前,它必須能夠訂閱瀏覽器 歷程堆疊 中的變更。

使用者瀏覽時,瀏覽器會維護自己的歷程堆疊。這就是「回上一頁」和「前往下一頁」按鈕運作的方式。在傳統網站(沒有 JavaScript 的 HTML 文件)中,只要使用者按下連結、提交表單或點選「回上一頁」或「前往下一頁」按鈕時,瀏覽器就會向伺服器發出請求。

例如,考慮下列使用者的操作:

  1. 按下連結前往 /dashboard
  2. 按下連結前往 /accounts
  3. 按下連結前往 /customers/123
  4. 按下「回上一頁」按鈕
  5. 按下連結前往 /dashboard

歷程堆疊會變為以下情況,其中 **粗體** 字體標示為目前的 URL 項目

  1. /dashboard
  2. /dashboard, /accounts
  3. /dashboard, /accounts, /customers/123
  4. /dashboard, /accounts, /customers/123
  5. /dashboard, /accounts, /dashboard

歷程物件

藉由使用**用戶端路由**,開發人員能夠以程式化方式處理瀏覽器的 歷程堆疊。例如,我们可以撰寫類似的程式碼,變更 URL,但沒有瀏覽器的預設行為向伺服器發出請求。

<a
  href="/contact"
  onClick={(event) => {
    // stop the browser from changing the URL and requesting the new document
    event.preventDefault();
    // push an entry into the browser history stack and change the URL
    window.history.pushState({}, undefined, "/contact");
  }}
/>

純粹做為示範,請不要在 React Router 中直接使用 window.history.pushState

這段程式碼會變更 URL,但不會對使用者介面產生任何影響。我們需要再撰寫一些能變更某些狀態的程式碼,才能讓使用者介面變更為聯絡人頁面。問題在於瀏覽器不會提供「監控 URL」並訂閱類似變更的方式給我們。

嗯,這並不完全正確。我們可以透過 pop 事件來監控 URL 的變更。

window.addEventListener("popstate", () => {
  // URL changed!
});

但這只會在使用者按下「回上一頁」或「前往下一頁」按鈕時觸發。當程式設計師呼叫 window.history.pushStatewindow.history.replaceState 時,並沒有對應的事件。

這就是專屬於 React Router 的 history 物件發揮作用的地方。它提供了一種「監控 URL」變更的方式,不論 歷程操作 是**push**(推入堆疊)、**pop**(彈出堆疊)或**replace**(取代堆疊)。

let history = createBrowserHistory();
history.listen(({ location, action }) => {
  // this is called whenever new locations come in
  // the action is POP, PUSH, or REPLACE
});

應用程式不需要設定自己的歷史物件,那是 <Router> 的工作。它會設定其中一個物件,訂閱 歷史堆疊 的變更,最後在 URL 變更時更新狀態。這會導致應用程式重新呈現並顯示正確的 UI。它需要放入狀態中的唯一事項是 位置,其餘所有項目都會由此單一物件運作。

位置

瀏覽器在 window.location 上有一個位置物件。它會告訴你關於 URL 的資訊,但也有某些變更它的方法

window.location.pathname; // /getting-started/concepts/
window.location.hash; // #location
window.location.reload(); // force a refresh w/ the server
// and a lot more

說明為例。你通常不會在 React Router 應用程式中使用 window.location

React Router 沒有使用 window.location,而是有一個基於 window.location 設計的 位置 概念,但它更簡單。它看起來像這樣

{
  pathname: "/bbq/pig-pickins",
  search: "?campaign=instagram",
  hash: "#menu",
  state: null,
  key: "aefz24ie"
}

前三項: { pathname, search, hash } 完全就像 window.location。如果你只新增這三項,就會取得使用者在瀏覽器中看到的 URL

location.pathname + location.search + location.hash;
// /bbq/pig-pickins?campaign=instagram#menu

最後兩項, { state, key },是 React Router 特定的。

位置路徑名稱

這是 URL 中來源之後的部分,因此對於 https://example.com/teams/hotspurs 路徑名稱是 /teams/hotspurs。這是路線比對的位置唯一部分。

位置查詢

許多人都使用許多不同的術語來表示 URL 的這個部分

  • 位置查詢
  • 查詢參數
  • URL 查詢參數
  • 查詢字串

在 React Router 中,我們稱它為「位置查詢」。不過,位置查詢是 URLSearchParams 的序列化版本。因此,有時我們也可能會稱它為「URL 查詢參數」。

// given a location like this:
let location = {
  pathname: "/bbq/pig-pickins",
  search: "?campaign=instagram&popular=true",
  hash: "",
  state: null,
  key: "aefz24ie",
};

// we can turn the location.search into URLSearchParams
let params = new URLSearchParams(location.search);
params.get("campaign"); // "instagram"
params.get("popular"); // "true"
params.toString(); // "campaign=instagram&popular=true",

精確來說,請將序列化的字串版本指稱為「查詢」,而將剖析的版本指稱為「查詢參數」,但當精確性不重要時,通常會交替使用這些術語。

位置雜湊

URL 中的雜湊表示 當前頁面 上的捲動位置。在 window.history.pushState API 導入之前,網頁開發人員專門使用 URL 的雜湊部分進行用戶端路由,它是我們可以操作的唯一部分而不會對伺服器進行新的請求。然而,今天我們可以使用它來達到其設計目的。

位置狀態

你可能想知道為什麼 window.history.pushState() API 稱為「推送狀態」。狀態?我們不只是變更 URL 而已嗎?它不應該是 history.push 嗎?好,當 API 設計時,我們不在現場,所以我們不知道為什麼「狀態」會成為重點,但它畢竟是瀏覽器很酷的功能。

瀏覽器讓我們可以透過傳遞值給 pushState 來保留關於導覽的資訊。當使用者按一下「返回」時,history.state 上的值會變更為之前「推入」的任何內容。

window.history.pushState("look ma!", undefined, "/contact");
window.history.state; // "look ma!"
// user clicks back
window.history.state; // undefined
// user clicks forward
window.history.state; // "look ma!"

說明範例。在 React Router 應用程式中,您不會直接讀取 `history.state`

React Router 利用這個瀏覽器功能,對其進行一些抽象化處理,並將值顯示在 `location` 中,而不是 `history`

您可以想像 `location.state`,就像 `location.hash` 或 `location.search`,只不過不是將值放在 URL 中,而是加以隱藏起來,就像 URL 中的超秘密部分,只有程式設計師會知道

`location state` 有幾個很好的使用案例

  • 告知下一個頁面使用者從何而來,並對 UI 進行分支。最常見的實作是在網格檢視中,使用者如果按某個項目,就顯示記錄中的 modal;但如果他們直接顯示在 URL 中,就以其自己的配置顯示記錄(Pinterest、舊版 Instagram)
  • 將部分記錄從清單傳送至下一個畫面,以便立即呈現部分資料,然後再擷取其餘資料

您有兩種方法可以設定 `location state`:在 `<Link>` 或 `navigate`

<Link to="/pins/123" state={{ fromDashboard: true }} />;

let navigate = useNavigate();
navigate("/users/123", { state: partialUser });

而在下一個頁面中,您可以使用 `useLocation` 來存取

let location = useLocation();
location.state;

`location state` 值會被序列化,因此類似 `new Date()` 的內容會轉成字串

位置金鑰

每個位置都會取得一個獨一無二的金鑰。這在進階案例中很有用,例如,基於位置的捲軸管理、用戶端資料快取等等。由於每個新位置都會取得一個獨一無二的金鑰,因此您可以建立在純粹物件、`new Map()` 甚至 `locationStorage` 中儲存資訊的抽象化處理方式

例如,一個非常基本的用戶端資料快取可以根據位置金鑰(以及擷取的 URL)儲存值,當使用者點選返回時,則略過撷取資料的程序

let cache = new Map();

function useFakeFetch(URL) {
  let location = useLocation();
  let cacheKey = location.key + URL;
  let cached = cache.get(cacheKey);

  let [data, setData] = useState(() => {
    // initialize from the cache
    return cached || null;
  });

  let [state, setState] = useState(() => {
    // avoid the fetch if cached
    return cached ? "done" : "loading";
  });

  useEffect(() => {
    if (state === "loading") {
      let controller = new AbortController();
      fetch(URL, { signal: controller.signal })
        .then((res) => res.json())
        .then((data) => {
          if (controller.signal.aborted) return;
          // set the cache
          cache.set(cacheKey, data);
          setData(data);
        });
      return () => controller.abort();
    }
  }, [state, cacheKey]);

  useEffect(() => {
    setState("loading");
  }, [URL]);

  return data;
}

配對

在初始呈現時和 歷史記錄堆疊變更時,React Router 會將 位置 與您的 路由設定 進行配對,以找出要呈現的一組 配對

定義路由

路由設定是一個 路由 樹狀結構,如下所示

<Routes>
  <Route path="/" element={<App />}>
    <Route index element={<Home />} />
    <Route path="teams" element={<Teams />}>
      <Route path=":teamId" element={<Team />} />
      <Route path=":teamId/edit" element={<EditTeam />} />
      <Route path="new" element={<NewTeamForm />} />
      <Route index element={<LeagueStandings />} />
    </Route>
  </Route>
  <Route element={<PageLayout />}>
    <Route path="/privacy" element={<Privacy />} />
    <Route path="/tos" element={<Tos />} />
  </Route>
  <Route path="contact-us" element={<Contact />} />
</Routes>

會遞迴處理 `<Routes>` 組件中的 `props.children`,去掉其屬性,並產生如下物件

let routes = [
  {
    element: <App />,
    path: "/",
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: "teams",
        element: <Teams />,
        children: [
          {
            index: true,
            element: <LeagueStandings />,
          },
          {
            path: ":teamId",
            element: <Team />,
          },
          {
            path: ":teamId/edit",
            element: <EditTeam />,
          },
          {
            path: "new",
            element: <NewTeamForm />,
          },
        ],
      },
    ],
  },
  {
    element: <PageLayout />,
    children: [
      {
        element: <Privacy />,
        path: "/privacy",
      },
      {
        element: <Tos />,
        path: "/tos",
      },
    ],
  },
  {
    element: <Contact />,
    path: "/contact-us",
  },
];

事實上,您可以使用 `useRoutes(routesGoHere)` 這個勾子函式,來取代 `<Routes>`。這正是 `<Routes>` 在做的事

如您所見,路由可以定義多個 區段,例如 `:teamId/edit`,或只定義一個,例如 `:teamId`。`<Routes>` 組件會將 路由設定 中沿著分支的所有路徑段加在一起,為路由建立最終的 路徑格式

配對參數

請注意 :teamId 區段。就是所謂 動態區段 路徑樣式,表示不會靜態比對 URL (實際字元) 而會動態比對。任何值都可以填入 :teamId。無論是 /teams/123/teams/cupcakes 都會比對成功。我們稱已剖析值為 URL 參數。因此,在此情況下,我們的 teamId 參數會是 "123""cupcakes"。我們會在 顯示 區段說明如何在應用程式中使用這些參數。

排名路線

如果我們加總 路由組態 所有分支中的所有區段,最後會得出以下路徑樣式,我們的應用程式會回應這些路徑樣式

[
  "/",
  "/teams",
  "/teams/:teamId",
  "/teams/:teamId/edit",
  "/teams/new",
  "/privacy",
  "/tos",
  "/contact-us",
];

這正是事情變得非常有趣的地方。考慮 URL /teams/new。清單中哪一個樣式比對得到該 URL?

沒錯,有兩個!

/teams/new
/teams/:teamId

此時 React Router 必須做出決定,因為答案只能有一個。許多路由器(包括客戶端路由器和伺服器端路由器)會依照定義路徑樣式的順序處理這些樣式。第一個比對成功的就會獲勝。在此情況下,我們會比對 / 並顯示 <Home/> 元件。這絕對不是我們想要的結果。此類路由器要求我們將路由排序得完美無缺,才能得到預期的結果。React Router 的運作方式一直到 v6 都是如此,但現在它聰明多了。

查看那些樣式時,您憑直覺就知道我們想要將 /teams/new 比對為 URL /teams/new。這是完美的比對!React Router 也知道。在比對時,它會根據區段數量、靜態區段、動態區段、星號樣式等,對您的路由進行評比,然後選擇最具體的比對。您再也不用考慮路由排序的問題了。

無路徑路由

您可能已經注意到稍早出現的奇怪路由

<Route index element={<Home />} />
<Route index element={<LeagueStandings />} />
<Route element={<PageLayout />} />

它們甚至沒有路徑,怎麼可能是路由?這正是 React Router 中的「路由」一詞被用得非常寬鬆的地方。<Home/><LeagueStandings/>索引路由,而 <PageLayout/>版面配置路由。我們會在 顯示 區段說明它們的運作方式。兩者都沒有什麼比對工作要進行。

路由比對

當路由比對得到 URL 時,會以 比對 物件表示。針對 <Route path=":teamId" element={<Team/>}/> 的比對會類似這樣

{
  pathname: "/teams/firebirds",
  params: {
    teamId: "firebirds"
  },
  route: {
    element: <Team />,
    path: ":teamId"
  }
}

pathname 儲存與此路由相符的 URL 部分(在本例中,為 URL 的所有部分)。params 儲存從任何 動態區段 中解析出的值(若有相符的動態區段)。請注意,參數物件的鍵會直接對應到區段的名稱::teamId 會變成 params.teamId

由於我們的路由是一棵樹,一個單一的 URL 可以與整棵樹的一個分支相符。考慮 URL /teams/firebirds,它將會是以下的路由分支

<Routes>
  <Route path="/" element={<App />}>
    <Route index element={<Home />} />
    <Route path="teams" element={<Teams />}>
      <Route path=":teamId" element={<Team />} />
      <Route path=":teamId/edit" element={<EditTeam />} />
      <Route path="new" element={<NewTeamForm />} />
      <Route index element={<LeagueStandings />} />
    </Route>
  </Route>
  <Route element={<PageLayout />}>
    <Route path="/privacy" element={<Privacy />} />
    <Route path="/tos" element={<Tos />} />
  </Route>
  <Route path="contact-us" element={<Contact />} />
</Routes>

React Router 會從這些路由和 url 建立一個 matches 陣列,因此它可以呈現出與路由巢狀結構相符的巢狀 UI。

[
  {
    pathname: "/",
    params: null,
    route: {
      element: <App />,
      path: "/",
    },
  },
  {
    pathname: "/teams",
    params: null,
    route: {
      element: <Teams />,
      path: "teams",
    },
  },
  {
    pathname: "/teams/firebirds",
    params: {
      teamId: "firebirds",
    },
    route: {
      element: <Team />,
      path: ":teamId",
    },
  },
];

呈現

最後一個概念是呈現。假設你的應用程式入口如下所示

const root = ReactDOM.createRoot(
  document.getElementById("root")
);
root.render(
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<App />}>
        <Route index element={<Home />} />
        <Route path="teams" element={<Teams />}>
          <Route path=":teamId" element={<Team />} />
          <Route path="new" element={<NewTeamForm />} />
          <Route index element={<LeagueStandings />} />
        </Route>
      </Route>
      <Route element={<PageLayout />}>
        <Route path="/privacy" element={<Privacy />} />
        <Route path="/tos" element={<Tos />} />
      </Route>
      <Route path="contact-us" element={<Contact />} />
    </Routes>
  </BrowserRouter>
);

讓我們再使用 /teams/firebirds URL 作為範例。<Routes> 將會將 位置 與你的 路由設定 配合,取得一組 matches,然後呈現出如下的 React 元素樹

<App>
  <Teams>
    <Team />
  </Teams>
</App>

每個在父路由元素內呈現的匹配都十分強大的抽象化。大多數網站及應用程式都具有以下特質:方塊包在方塊內、方塊再包在方塊內,每個都有導覽區段,且可以變更頁面上某一個子區段。

輸出位置

這個巢狀元素樹不會自動發生。<Routes> 會為你的第一個匹配項目呈現元素(在本例中,是 <App/>)。下一個匹配項目的元素是 <Teams>。為了呈現它,App 需要呈現一個 輸出位置

function App() {
  return (
    <div>
      <GlobalNav />
      <Outlet />
      <GlobalFooter />
    </div>
  );
}

Outlet 元件會隨時呈現下一個匹配。這表示 <Teams> 也需要一個輸出位置來呈現 <Team/>

如果 URL 是 /contact-us,則元素樹就會變更為

<Contact />

因為連絡人表單不在主要的 <App> 路由之下。

如果 URL 是 /teams/firebirds/edit,則元素樹就會變更為

<App>
  <Teams>
    <EditTeam />
  </Teams>
</App>

輸出位置會將子項切換為與新子項相符的子項,但是父層配置會維持不變。這個技術十分細微,但是可以有效地清理元件。

索引路由

請記住 /teams路由設定

<Route path="teams" element={<Teams />}>
  <Route path=":teamId" element={<Team />} />
  <Route path="new" element={<NewTeamForm />} />
  <Route index element={<LeagueStandings />} />
</Route>

如果 URL 為 /teams/firebirds,則元素樹會是

<App>
  <Teams>
    <Team />
  </Teams>
</App>

但是,如果 URL 為 /teams,則元素樹會是

<App>
  <Teams>
    <LeagueStandings />
  </Teams>
</App>

聯盟排名怎麼會這樣冒出來?<Route index element={<LeagueStandings>}/> 是怎麼突然冒出來的?它甚至沒有路徑!這是因為它是一個 索引路由。索引路由會在父路由的 輸出位置 中,以父路由的路徑呈現。

以這種方式思考:如果您不在其中一個子路由的路徑中, 則 <Outlet> 會在 UI 中不顯示任何內容

<App>
  <Teams />
</App>

如果所有小組都在左側的清單中,則空插座表示右邊有一個空白頁面! 您的 UI 需要些內容來填補空白:索引路由來救援。

思考索引路由的另一種方法是,它是父路由匹配但其子路由都未匹配時預設的子路由。

根據使用者介面,您可能不需要索引路由,但是如果父路由有任何類型的持續導航, 當使用者尚未按一下任一個項目時,您很可能需要一個索引路由來填補空白。

版面路由

以下是我們尚未匹配的路由配置的一部分: /privacy。 我們再次查看路由配置,並重點顯示匹配的路由

<Routes>
  <Route path="/" element={<App />}>
    <Route index element={<Home />} />
    <Route path="teams" element={<Teams />}>
      <Route path=":teamId" element={<Team />} />
      <Route path=":teamId/edit" element={<EditTeam />} />
      <Route path="new" element={<NewTeamForm />} />
      <Route index element={<LeagueStandings />} />
    </Route>
  </Route>
  <Route element={<PageLayout />}>
    <Route path="/privacy" element={<Privacy />} />
    <Route path="/tos" element={<Tos />} />
  </Route>
  <Route path="contact-us" element={<Contact />} />
</Routes>

並將會呈現出的結果元素樹會是

<PageLayout>
  <Privacy />
</PageLayout>

別忘了將 <Outlet> 新增到版面中,您希望在其中顯示子路由元素。 使用 {children} 將無法達到預期效果。

PageLayout 路由確實很奇怪。 我們稱之為 版面路由,因為它根本不參與匹配(儘管其子路由有)。它的存在只是為了讓將多個子路由包裝在同一個版面中變得更簡單。 如果我們不允許這樣做,您將必須以兩種不同的方式處理版面:有時您的路由會為您執行,有時您會手動執行,而您的應用程式中到處都是大量的版面元件重複。

您可以這樣做,但我們建議使用版面路由

<Routes>
  <Route path="/" element={<App />}>
    <Route index element={<Home />} />
    <Route path="teams" element={<Teams />}>
      <Route path=":teamId" element={<Team />} />
      <Route path=":teamId/edit" element={<EditTeam />} />
      <Route path="new" element={<NewTeamForm />} />
      <Route index element={<LeagueStandings />} />
    </Route>
  </Route>
  <Route
    path="/privacy"
    element={
      <PageLayout>
        <Privacy />
      </PageLayout>
    }
  />
  <Route
    path="/tos"
    element={
      <PageLayout>
        <Tos />
      </PageLayout>
    }
  />
  <Route path="contact-us" element={<Contact />} />
</Routes>

所以,是的,版面「路由」的語意有點愚蠢,因為它與 URL 匹配無關,但它實在太方便了,無法禁止。

URL 更改時,我們稱之為「導航」。 在 React Router 中有兩種導航方式

  • <Link>
  • 導航

這是主要的導航方式。 瀏覽 <Link> 使用者可以在按一下時變更 URL。 React Router 將防止瀏覽器的預設行為並告訴 記錄 ,將一個新項目推入 記錄堆疊位置 發生變更,新的 匹配 會顯示。

但是,連結是可存取的,因為它們

  • 仍會顯示 <a href>,因此符合所有預設的可存取性問題(例如鍵盤、焦點、SEO 等)
  • 如果右鍵按一下或按住 command/control 鍵按一下並「在新分頁開啟」,則不會防止瀏覽器的預設行為

嵌套的路由 不僅只處理瀏覽版面;它們還會啟用「相對連結」。 想想我們之前的 teams 路由

<Route path="teams" element={<Teams />}>
  <Route path=":teamId" element={<Team />} />
</Route>

<Teams> 元件可以顯示類似的連結

<Link to="psg" />
<Link to="new" />

連結到的完整路徑會是 /teams/psg/teams/new。它們繼承渲染所在的路徑。如此一來,您的路徑組件就不必真正知道應用程式內其他路徑的任何資訊。大量的連結只會遞進到多一個 區段。您可重新排列您的整個 路徑設定,而這些連結仍可能完全正常運作。這在您一開始建立網站並設計和版面四處變動時非常有幫助。

此函式傳回自 useNavigate 鉤子,並允許您,程式設計人員,在任何時候變更 URL。您可以在逾時時執行此操作

let navigate = useNavigate();
useEffect(() => {
  setTimeout(() => {
    navigate("/logout");
  }, 30000);
}, []);

或是在提交表單後

<form onSubmit={event => {
  event.preventDefault();
  let data = new FormData(event.target)
  let urlEncoded = new URLSearchParams(data)
  navigate("/create", { state: urlEncoded })
}}>

就像 Linknavigate 也與巢狀「至」值配合使用。

navigate("psg");

您應該有正當理由使用 navigate,而不是 <Link>。這讓我們感到非常難過

<li onClick={() => navigate("/somewhere")} />

除了連結和表單外,極少數互動應變更 URL,因為它會提高無障礙性和使用者預期的複雜性。

資料存取

最後,應用程式將希望向 React Router 索取幾則資訊,以建構出完整的 UI。為此,React Router 有一堆鉤子

let location = useLocation();
let urlParams = useParams();
let [urlSearchParams] = useSearchParams();

複習

讓我們一次從頭到尾說起!

  1. 您渲染您的應用程式

    const root = ReactDOM.createRoot(
      document.getElementById("root")
    );
    root.render(
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<App />}>
            <Route index element={<Home />} />
            <Route path="teams" element={<Teams />}>
              <Route path=":teamId" element={<Team />} />
              <Route path="new" element={<NewTeamForm />} />
              <Route index element={<LeagueStandings />} />
            </Route>
          </Route>
          <Route element={<PageLayout />}>
            <Route path="/privacy" element={<Privacy />} />
            <Route path="/tos" element={<Tos />} />
          </Route>
          <Route path="contact-us" element={<Contact />} />
        </Routes>
      </BrowserRouter>
    );
    
  2. <BrowserRouter> 建立一個 歷程,將初始的 位置 置於狀態中,並訂閱 URL

  3. <Routes> 遞迴了它的 子路徑 以建構 路徑設定、比對這些路徑與 位置,建立一些路徑 比對,並渲染第一個比對的路徑元素。

  4. 您在每個 父路徑 中渲染 <Outlet/>

  5. 出口渲染路徑 比對 中的下一項比對。

  6. 使用者按下連結

  7. 連結呼叫 navigate()

  8. 歷程 變更 URL 並通知 <BrowserRouter>

  9. <BrowserRouter> 重新渲染,從 (2) 開始!

就是這樣!我們希望本指南有助於您更深入瞭解 React Router 中的主要概念。