突然懷念起以前手刻麵包屑的時光。
最基本的結構
基本結構會由 <Breadcrumb>
包 <Breadcrumb.Item>
來組成。
而 <Breadcrumb.Item>
裡面可以放我們想放的元件,例如 <a>
或 <Link>
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import React from 'react' import { Breadcrumb } from 'antd'
function App() { return ( <> <h2>Bread crumb</h2> <Breadcrumb> <Breadcrumb.Item>Home</Breadcrumb.Item> <Breadcrumb.Item> <a href='#'>App</a> </Breadcrumb.Item> <Breadcrumb.Item> <a href='#'>List</a> </Breadcrumb.Item> <Breadcrumb.Item> <a href='#'>Detail</a> </Breadcrumb.Item> </Breadcrumb> </> ) }
export default App
|
輸出結果:
因為是麵包屑,所以最後一個 item
會自動加上 active 的樣式(最後一個等於目前所在位置),不用自己做處理。
指定間隔符
可以用 separator
這個屬性來處理:
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 React from 'react' import { Breadcrumb } from 'antd'
function App() { return ( <> <h2>Bread crumb</h2> {/* 指定為 > */} <Breadcrumb separator='>'> <Breadcrumb.Item>Home</Breadcrumb.Item> <Breadcrumb.Item> <a href='#'>App</a> </Breadcrumb.Item> <Breadcrumb.Item> <a href='#'>List</a> </Breadcrumb.Item> <Breadcrumb.Item> <a href='#'>Detail</a> </Breadcrumb.Item> </Breadcrumb> </> ) }
export default App
|
當然也可以放入元件:
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
| import React from 'react' import { Breadcrumb } from 'antd' import { CaretRightOutlined } from '@ant-design/icons'
function App() { return ( <> <h2>Bread crumb</h2> {/* 放入 icon 元件 */} <Breadcrumb separator={<CaretRightOutlined />}> <Breadcrumb.Item>Home</Breadcrumb.Item> <Breadcrumb.Item> <a href='#'>App</a> </Breadcrumb.Item> <Breadcrumb.Item> <a href='#'>List</a> </Breadcrumb.Item> <Breadcrumb.Item> <a href='#'>Detail</a> </Breadcrumb.Item> </Breadcrumb> </> ) }
export default App
|
輸出結果:
與 React 路由做連結
這個我覺得剛開始碰的話有點小複雜,所以不太懂的話建議跟著實作看看。
先來看一下最後的成果:
主要的思路是這樣:
- 取得目前所在的路徑
- 把路徑拆開來,例如
/app/list
, 會被拆成 ['app', list]
- 利用第二步的陣列來產生網址
['app', 'list']
=> /app
、/app/list
- 建立一個 map,一個網址會對應到一個名稱(用來顯示在麵包屑上的)
- 利用上面產生的「網址」和「map」來產生麵包屑元件
接著就來看 code 吧:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| import React from 'react' import { Breadcrumb } from 'antd' import { CaretRightOutlined } from '@ant-design/icons' import { Routes, Route, Link, Outlet, useLocation } from 'react-router-dom' import Home from './Home' import App from './App' import List from './List' import Detail from './Detail'
interface NameMap { [key: string]: string }
const breadcrumbNameMap: NameMap = { '/app': 'App', '/app/list': 'List', '/app/list/detail': 'Detail' }
function Main() { const location = useLocation()
const pathSnippets = location.pathname.split('/').filter((i) => i)
const extraBreadcrumbItems = pathSnippets.map((_, index) => { const url = `/${pathSnippets.slice(0, index + 1).join('/')}` return ( <Breadcrumb.Item key={url}> <Link to={url}>{breadcrumbNameMap[url]}</Link> </Breadcrumb.Item> ) })
const breadcrumbItems = [ <Breadcrumb.Item key={'Home'}> <Link to=''>Home</Link> </Breadcrumb.Item> ].concat(extraBreadcrumbItems)
return ( <> <Breadcrumb style={{ marginBottom: '30px' }} separator={<CaretRightOutlined />} > {/* 最後把產生好的 item 放進來就可以了 */} {breadcrumbItems} </Breadcrumb>
<Routes> <Route path='' element={<Outlet />}> <Route path='' element={<Home />} /> <Route path='app' element={<Outlet />}> <Route path='' element={<App />} /> <Route path='list' element={<Outlet />}> <Route path='' element={<List />} /> <Route path='detail' element={<Detail />} /> </Route> </Route> </Route> </Routes> </> ) }
export default Main
|
至於路由的部分看不太懂的話,可以參考我之前寫的這篇。
另一種做法,抽出去當成一個元件
剛剛的做法其實有個問題,就是沒辦法處理「動態路由」的 mapping。
回顧一下,當時 map 的部分我們是這樣寫的:
1 2 3 4 5 6
| const breadcrumbNameMap = { '/app': 'App', '/app/list': 'List', '/app/list/detail': 'Detail' }
|
問題在於,如果現在網址是 /app/list/:id
的話,map 要怎麼寫?好像就沒辦法了,雖然硬要的話是可以寫成這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const breadcrumbNameMap = { '/app': 'App', '/app/list': 'List' }
const pathSnippets = location.pathname.split('/').filter((i) => i)
if (parseInt(pathSnippets[pathSnippets.length - 1], 10)) { for (let i = 1; i <= parseInt(pathSnippets[pathSnippets.length - 1], 10); i++) { breadcrumbNameMap[`/app/list/${i}`] = `List of ${i}` } }
|
附註:有興趣的話可以到這邊參考範例
雖然這也不失為一種做法,不過如果想更有彈性一些的話,可以試著用「抽出去變成元件」這種思維來做。
這會有點類似 HOC 的感覺。簡單來說,我們可以自製一個元件,它會接收一個 props,這個 props 要包含這些資訊:
1 2 3 4 5
| type Paths = { url: string labal: string access: boolean }
|
而這元件就會根據 props 來產生對應的麵包屑元件:
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 29 30 31 32 33 34 35 36
| import { Breadcrumb } from 'antd' import { Link } from 'react-router-dom' import React from 'react'
export type Paths = { url: string label: string access: boolean }
interface Props { paths: Paths[] }
const PageBreadcrumb: React.FC<Props> = ({ paths }) => { const Breadcrumbitems = paths.map((path) => { return ( <Breadcrumb.Item key={path.url}> {path.access ? <Link to={path.url}>{path.label}</Link> : path.label} </Breadcrumb.Item> ) }) return ( <Breadcrumb style={{ marginBottom: '30px' }} > {Breadcrumbitems} </Breadcrumb> ) }
export default PageBreadcrumb
|
接著只要在對應的元件上使用就行了:
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 29 30 31 32 33 34 35 36 37 38 39
| import React from 'react' import { Link } from 'react-router-dom' import PageBreadcrumb, { Paths } from '../PageBreadcrumb'
const List: React.FC = () => { const breadcrumpPaths: Paths[] = [ { url: '/', label: 'Home', access: true }, { url: '/app', label: 'App', access: true }, { url: '/app/list', label: 'List', access: true } ] return ( <> <PageBreadcrumb paths={breadcrumpPaths} /> <ul> {Array.from({ length: 10 }, (_, index) => { return ( <li key={index}> <Link to={(index + 1).toString()}>List item {index + 1}</Link> </li> ) })} </ul> </> ) }
export default List
|
這樣的缺點是每一個頁面都得各別處理麵包屑元件,但換來的好處是可以有更大的彈性。總之我覺得要用哪一種方法都可以,端看你偏好哪個,最後一樣附上範例。