終究都要學 React 何不現在學呢? - React Router - 巢狀路由 - (25)

前言

前面我們已經認識了基本的 React Router,接著我們要認識另一個觀念,也就是巢狀路由,而巢狀路由在實戰開發上是很常見的,因此這一篇我們就必須要來認識一下。

切換頁面

在開始介紹巢狀路由(Nested Routes)之前,我們要先認識一下 React Router 中的另一個東西也就是 Link,在前面的練習結尾處,我們用了一個很蠢的方式去切換路由,也就是直接透過瀏覽器的網址列輸入 /todolist 到 ToDoList 頁面,不得不說這種方式很蠢,你也不可能讓使用者用這種方式去切換路由對吧?那…實際上來講我們該怎麼做才對呢?

正確來講我們要使用 React Router 的 Link 來切換我們想要到達的頁面,所以這邊就先打開 main.jsx,先在這邊暫時加入 List 即可。

使用方式很簡單直接將 List 放在 Routes 外面就可以了,而這邊我稍微做了一下簡單版的導覽列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React from 'react'
import { HashRouter, Route, Routes, Link } from "react-router-dom";
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import ToDoList from "./TodoList";

ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<HashRouter>
<nav className="px-5 flex items-center h-[60px] bg-indigo-500 text-white">
<h1 className="mr-auto text-2xl">React TodoList</h1>
<ul className="flex">
<li className="mr-3">
<Link to="/" className="border p-3 hover:bg-indigo-600 duration-500">Home</Link>
</li>
<li>
<Link to="/todolist" className="border p-3 hover:bg-indigo-600 duration-500">ToDoList</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={ <App /> } />
<Route path="/todolist" element={ <ToDoList /> } />
</Routes>
</HashRouter>
</React.StrictMode>
)

請注意 Link 必須放在 HashRouter 裡面,否則噴這個錯誤訊息

Uncaught Error: useHref() may be used only in the context of a Router component.

那麼拉回到剛剛講到一半的部分,上面我們新增了兩個 Link,一個是首頁跟 ToDoList 頁面,接下來當你點上方任何一個連結都會發現下方畫面會跟著切換

切換頁面

我們可以看到 Link 的使用方式非常簡單,只需要戴上一個 to 參數並設定與 RoutePath 屬性相同名稱就可以成功到該頁面了。

有沒有覺得很像 Vue Router 的 router-link 呢?簡單讓你回憶一下 Vue Router 的導航切換方式。

1
<router-link to="/todolist">ToDoList</router-link>

巢狀路由

回到巢狀路由的部分,我們會發現有一個問題存在,我們所有的頁面都是在 / 底下,可是實際開發來講不可能通通都在 /,一定還會有 admindashboard 等等路由,然後掛在這些路由底下,舉例來講我們預期 Url 會變化成以下

1
2
https://www.example.com/#/admin
https://www.example.com/#/admin/products

這時候你可能會想說…「那 Route 就這樣寫啊?有問題嗎?」

1
2
3
4
5
6
<Routes>
<Route path="/" element={ <App /> } />
<Route path="/todolist" element={ <ToDoList /> } />
<Route path="/admin" element={ <ToDoList /> } />
<Route path="/admin/products" element={ <ToDoList /> } />
</Routes>

確實這樣子寫是可以達到前面所提的效果,但是 admin 的頁面與 user 的頁面必定會有一些不同,例如 admin 有自己的導覽列與 user 必定是不同的,總不可能 user 可以看到 admin 的功能操作吧?如果你真的這樣做的話,那麼就會導致所有頁面都吃到同一種導覽列,因此這邊就會需要使用到巢狀路由來達到 Layout 的概念。

那麼我們該如何撰寫巢狀路由呢?其實很簡單,你只需要將 Route 包在 Route 中就可以了,如下

1
2
3
4
5
6
7
8
<Routes>
<Route path="/" element={ <App /> } >
<Route path="todolist" element={ <ToDoList /> } />
</Route>
<Route path="/admin" element={ <Admin /> } >
<Route path="products" element={ <AdminProducts /> } />
</Route>
</Routes>

接下來為了讓大家體感上更明顯一點,所以這邊請你將剛剛寫在 main.jsx 的導覽列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<nav className="px-5 flex items-center h-[60px] bg-indigo-500 text-white">
<h1 className="mr-auto text-2xl">React TodoList</h1>
<ul className="flex">
<li className="mr-3">
<Link to="/" className="border p-3 hover:bg-indigo-600 duration-500">Home</Link>
</li>
<li>
<Link to="/todolist" className="border p-3 hover:bg-indigo-600 duration-500">ToDoList</Link>
</li>
<li>
<Link to="/admin" className="border p-3 hover:bg-indigo-600 duration-500">Admin</Link>
</li>
<li>
<Link to="/admin/products" className="border p-3 hover:bg-indigo-600 duration-500">AdminProducts</Link>
</li>
</ul>
</nav>

全部移動到 App.jsx 中,並將後台頁面的 Link 改成 /admin 就好,另一個 /admin/products 則刪除,因此目前 App.jsx 呈現如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { Link } from "react-router-dom";

const App = () => {
return (
<>
<nav className="px-5 flex items-center h-[60px] bg-indigo-500 text-white">
<h1 className="mr-auto text-2xl">React TodoList</h1>
<ul className="flex">
<li className="mr-3">
<Link to="/" className="border p-3 hover:bg-indigo-600 duration-500">Home</Link>
</li>
<li className="mr-3">
<Link to="/todolist" className="border p-3 hover:bg-indigo-600 duration-500">ToDoList</Link>
</li>
<li className="mr-3">
<Link to="/admin" className="border p-3 hover:bg-indigo-600 duration-500">Admin</Link>
</li>
</ul>
</nav>
<h1>App</h1>
</>
)
}

export default App;

這時候你應該會發現畫面滿空的,而且你點 ToDoList 也不會出現在畫面上,這邊原因是因為還要額外加上一個東西叫做 <Outlet /> 在 App.jsx,例如:放在 <h1>App</h1> 底下

1
2
3
4
5
6
import { Link, Outlet } from "react-router-dom";

...// 略過
<h1>App</h1>
<Outlet />
...// 略過

放上 <Outlet /> 之後,你就可以看到畫面正常的切換了

切換頁面

當你點擊 Admin 時,你也會發現頁面就是很純粹的空白一片,不會有前台的 Navbar 出現,只會出現後台專用的 Navbar(我有微調 Admin.jsx),而這就是共用 Layout 的概念,也是巢狀路由概念

範例程式碼:React Router Example

後記

本文將會同步更新到我的部落格