Sean's Blog

An image showing avatar

Hi, I'm Sean

這裡記錄我學習網站開發的筆記
歡迎交流 (ゝ∀・)b

LinkedInGitHub

React Router V6 - Setup Routes

React Router 團隊最近又來一次全面升級,新增了許多功能,特別是 Loader 功能實在是讓我大開眼界。除此之外,我發現他們的文件寫得很好,基本上只要跟著走一遍 Tutorial 就可以掌握新版的寫法調整與新功能哩。

不多說,讓我們重新開始,就從建立路由開始吧 ⋯⋯ ( Ꙭ)🥕

資料夾結構

首先,我們的 React 專案都有一個 src 資料夾,裡面會有一個進入點 (Entry),可能叫做 App.js(x) 或是 main.js(x),我們會在這裡配置路由。

接著,可以在 src 底下建立 /pages/routes 資料夾,用途是放置 Page-Level 的元件。

Folder Structure 其實沒有什麼特殊的變更,直接開始建立路由吧!

建立路由 - createBrowserRouter

React Router v6.4 新增的 Data APIs 提供了幾個創建路由的新方式,我們使用其中的 createBrowserRouter() 方法來建立路由系統。

此方法需要傳入一個由物件所組成的陣列(每一個物件就是一組路由),然後把回傳值提供給 RouterProvider

1import { createBrowserRouter, RouterProvider } from 'react-router-dom';
2import HomePage from '@/routes/Home';
3import ProductsPage from '@/routes/Products';
4
5const router = createBrowserRouter([
6  { path: '/', element: <HomePage /> },
7  { path: '/products', element: <ProductsPage /> },
8]);
9
10ReactDOM.createRoot(document.getElementById('root')).render(
11  <React.StrictMode>
12    <RouterProvider router={router} />
13  </React.StrictMode>,
14);
15

還有另一種建立方法 createRoutesFromElement 個人覺得較為冗長,且跟之前使用 JSX 的寫法相似,這裡就不予介紹啦。

路由鏈結 - Link 與 NavLink

在 React Router 中要實現頁面跳轉的功能,我們不是使用原生的 <a> 標籤,而是使用 React Router 提供的 <Link> 元件。

1import { Link, NavLink } from 'react-router-dom';
2<Link to="/products">Products Page</Link>;
3

另一種路由鏈結是 <NavLink>,它與 <Link> 的作用相同,但是還內建了 isActiveisPending 兩個狀態屬性。

你可以將它們解構出來,用於判斷當前路由的狀態,並且動態地設置樣式。

1const getNavLinkClass = ({ isActive, isPending }) => {
2  if (isActive) {
3    return 'active';
4  }
5  if (isPending) {
6    return 'pending';
7  }
8  return '';
9};
10
11<NavLink to="/products" className={getNavLinkClass}>
12  Products Page
13</NavLink>;
14

在製作巢狀路由時,可以為 <NavLink> 加上 end 屬性,以確保該元件不會在進入其子路由時,也被匹配到 isActive 等狀態,例如:

  • Home 只會對應到網站的根路由 ('/'),也就是後面不帶任何路徑時
  • Blog 只會對應到 /blog,當進入 /blog/hello-world 頁面時不會有 isActive 狀態
1<NavLink to="/" end>Home</NavLink>
2<NavLink to="/blog" end>Blog</NavLink>
3<NavLink to={`/blog/hello-world`}>Hello World</NavLink>
4

共用外框 - children 與 Outlet

一個網站通常會有一個共用的外框,例如上方或左側的導覽列、商標、版權宣告等等,這些內容會全部放在一個 Layout 元件當中。

以下的 RootLayout 元件是一個父路由,我們會使用 children 配置子路由,而那些子頁面的內容就會顯示在 <Outlet /> 這個輸出點的位置。

1import { Outlet } from 'react-router-dom';
2
3const RootLayout = () => {
4  return (
5    <>
6      <h1>Root Layout</h1>
7      <Outlet />
8    </>
9  );
10};
11
12export default RootLayout;
13

底下的範例,我們可以看到 RootLayout 的擺放位置

巢狀路由的相對與絕對路徑

路由配置上,使用 "/" 開頭的路徑為絕對路徑,通常父路由會使用絕對路徑,子路由使用相對路徑並且依賴於父路由。例如:父路由為 "/root" 等於網址 my-domain/root,而子路由設定 "products" 等於網址 /root/products

另外,還有一個小地方可以改善。下方範例中,可以看到子路由的 HomePage 與父路由其實是指向同一個路由。這種情況下,我們可以把 path: "" 改用 index 這個特殊的屬性去定義,明確地表示 HomePage 是一個 Index Route。

1const router = createBrowserRouter([
2  {
3    path: '/', // 絕對路徑
4    element: <RootLayout />,
5    children: [
6      // 相對路徑
7      // { path: "", element: <HomePage /> },
8      { index: true, element: <HomePage /> },
9      { path: 'products', element: <ProductsPage /> },
10    ],
11  },
12]);
13

使用 Index Route 會讓程式的語意更加明確,但是不使用也沒關係喔。

錯誤頁面 - errorElement

如果進入一個未定義的路由,通常會顯示 404 錯誤頁面,並且頁面上可能會有一些錯誤訊息。

製作專案時,我們通常不會直接使用 React Router 內建的錯誤頁面,而是選擇建立一個屬於自己專案的錯誤畫面。

1import { useRouteError } from 'react-router-dom';
2
3const ErrorPage = () => {
4  const error = useRouteError();
5  console.error(error);
6
7  return (
8    <div id="error-page">
9      <h1>Oops!</h1>
10      <p>Sorry, an unexpected error has occurred.</p>
11      <p>
12        <i>{error.statusText || error.message}</i>
13      </p>
14    </div>
15  );
16};
17
18export default ErrorPage;
19

完成了這個簡單的 404 頁面後,可以透過 errorElement 來配置這個錯誤處理的專屬頁面。

1const router = createBrowserRouter([
2  {
3    path: '/',
4    element: <RootLayout />,
5    errorElement: <ErrorPage />, // catch any errors
6    children: [
7      { index: true, element: <HomePage /> },
8      { path: 'products', element: <ProductsPage /> },
9    ],
10  },
11]);
12

程式化導頁 - useNavigate

除了使用 <Link> 來換頁,我們也可以手動地去執行路由跳轉的動作,例如:在表單送出或按下按鈕後導頁。

透過 react-router-dom 提供的 useNavigate 函式,就可以執行程式化導頁。

1import { useNavigate } from 'react-router-dom';
2
3const Home = ({ film }) => {
4  const navigate = useNavigate();
5
6  const handleNavigate = () => {
7    navigate('/products');
8  };
9
10  return (
11    <div>
12      <button onClick={handleNavigate}>前往產品頁面</button>
13    </div>
14  );
15};
16

動態路由與 useParams

動態路由在許多網站中都很常見,例如電商網站的所有商品頁面,點擊任一商品後便會進入商品明細頁。

在 React Router 中可以使用冒號 (:) 代表該片段屬於一個動態路由。

1const router = 'createBrowserRouter'([
2  {
3    path: '/',
4    element: <RootLayout />,
5    errorElement: <ErrorPage />,
6    children: [
7      { index: true, element: <HomePage /> },
8      { path: 'products', element: <ProductsPage /> },
9      { path: 'products/:productId', element: <ProductDetailPage /> },
10    ],
11  },
12]);
13

接下來,我們可以透過 React Router 提供的 useParams Hook 去取得這個動態路由片段的資訊物件。

這裡的 Identifier (productId) 是來自於路由設定裡的 /product/:productId,因此如果路由配置更改,這裡取得的 Identifier 也會跟著改變喔。

1import { useParams, Link } from 'react-router-dom';
2
3const DUMMY_PRODUCTS = [
4  { id: '1', title: 'Product 1' },
5  { id: '2', title: 'Product 2' },
6];
7
8const ProductDetailPage = () => {
9  const params = useParams();
10
11  return (
12    <div>
13      <h1>Product Detail</h1>
14      <p>{params.productId}</p>
15      {DUMMY_PRODUCTS.map((product) => (
16        <li key={product.id}>
17          <Link to={`/products/${product.id}`}>{product.title}</Link>
18        </li>
19      ))}
20    </div>
21  );
22};
23
24export default ProductDetailPage;
25

回顧

看完這篇文章,我們認識了 React Router V6 的基礎架構,瞭解如何使用它建立一套路由系統,並且能實際應用在專案上。

下一篇文章會介紹 React Router V6 這次新增的 Loader 等等全新功能,讓我們的路由系統更加全面。

References