# React router 路由的用途: 將每個分頁做成元件,並用路由管理URL來決定要顯示的頁面(元件) 而不是重新載入整個頁面,在重新整理時也會維持原本的狀態。 **製作router的步驟**: 0.環境建置-npm載入、加入browser 1.準備元件 2.撰寫router 3.加入連結 --- ## React router環境建置 先使用vite建立環境 (可參考[環境建置筆記](https://hackmd.io/@jadesnote/Sy6EbEB81e)) ### 0.使用npm載入React router ``` npm i react-router-dom ``` ### 0-1.加入BrowserRouter 在**main.jsx**檔案中加入BrowserRouter元件 > 每個專案檔做一次即可 ``` import {BrowserRouter} from "react-router-dom" 若後續要傳至github 建議使用HashRouter ``` 並將此元件包在App元件的外層 ``` <StrictMode> <BrowserRouter> <App /> </BrowserRouter> </StrictMode>, ``` ### 1.準備元件 在src下新增兩個資料夾 分別是:component用來存放樣式類的元件,以及pages用來存放分頁元件 > 以下新增navbar以及home、about作為範例 #### 一、新增navbar元件 載入bootstrap後在component資料夾下新增navbar元件, ``` function Navbar (){ return(<nav className="navbar navbar-expand-lg navbar-light bg-light"> <div className="container-fluid"> <a className="navbar-brand" href="#">Navbar</a> <div className="collapse navbar-collapse" id="navbarSupportedContent"> <ul className="navbar-nav me-auto mb-2 mb-lg-0"> <li className="nav-item"> <a className="nav-link" to="/" >Home</a> </li> <li className="nav-item"> <a className="nav-link" to="/about" >about</a> </li> </ul> </div> </div> </nav> ) } export default Navbar ``` 並將Navbar元件加入App.jsx檔中 #### 二、新增首頁和關於頁面元件 在pages下新增兩個分頁頁面 ``` function Home() { return( <div> 這是首頁 </div> ) } export default Home ``` ### 2.將路由加入App元件中 #### 一、將上一步所新增的元件都import到App元件中 #### 二、在App元件中import Routes、Route ``` import {Routes,Route} from "react-router-dom" ``` #### 三、加入Routes、Route ``` function App() { return ( <> <Navbar/> <div className="container mt-2"> <Routes> <Route path="/" element={<Home/>}></Route> <Route path="/about" element={<About/>}></Route> </Routes> </div> </> ) } 解析:Rotes包在Route外層,path為路徑後方加入的文字, 並把分頁元件加入element當中 ``` ### 3.加入連結 回到Navbar.jsx,加入Link ``` import { Link } from "react-router-dom" ``` 並把a連結標籤改為Link元件 ``` <li className="nav-item"> <Link className="nav-link" to="/" >Home</Link> </li> 解析:to後面加入的連結需與對應的route的path相同 ``` --- ## 巢狀路由寫法 ### 1.準備元件 在pages資料夾下 新增AlbumLayout及AlbumIndex兩個元件 ### 2.將元件加入App元件中 ``` import AlbumLayout from './pages/AlbumLayout' import AlbumIndex from './pages/AlbumIndex' <Route path='/album' element={<AlbumLayout />}> <Route index element={<AlbumIndex />}></Route> </Route> 注意:AlbumIndex的Route是放在AlbumLayout的Route中 筆記:如果是預設路徑可以直接使用index ``` ### 3.使用Outlet元件 回到AlbumLayout.jsx ``` import { Outlet } from "react-router-dom" ``` 並在預計要放入AlbumIndex的位置加入<Outlet/> --- ## 動態路由寫法 ### 1. 準備元件 在pages資料夾下加入 AlbumPhoto.jsx,並import進App元件中 ### 2. 將元件加入App元件中 ``` <Route path="/album" element={<AlbumLayout />}> <Route index element={<AlbumIndex />}></Route> <Route path=":id" element={<AlbumPhoto/>}></Route> </Route> 筆記:寫法同巢狀路由,path改用":",名稱可自訂 ``` ### 3. AlbumPhoto元件 #### 一、加入useParams ``` import { useParams } from "react-router-dom" const {id} = useParams(); 筆記:{}裡的名稱同path ``` #### 二、串Api ``` import axios from "axios"; const api = "https://api.unsplash.com/photos"; const accessId = import.meta.env.VITE_UNSPLASH_ACCESS; 元件內: const [photo,setPhoto] = useState({}) useEffect (()=>{ (async()=>{ try { const response = await axios.get(`${api}/${id}?client_id=${accessId}`); console.log(response); setPhoto(response.data) } catch (error) { console.log(error); } } )(); },[id]) 筆記:useEffect的觸發條件須帶入id才能動態更新畫面 ``` ### 4. AlbumLayout元件 #### 一、加入Link元件 #### 二、使用map渲染list畫面 ``` {list.map((item)=>{ return <li key={item.id}><Link to={item.id}>{item.id}</Link></li> })} ``` #### 三、如何讓相簿主頁也能顯示相同的list 加入context ``` <Outlet context={list}/> ``` 到AlbumIndex.jsx ``` import { useOutletContext } from "react-router-dom" 元件內: const list = useOutletContext(); return( <div>這是相簿主頁 {list.map((item)=>{ return <li key={item.id}>{item.id}</li>})} </div> ) ``` --- ## React router功能實作-搜尋功能 ### 1.基礎功能製作 #### 一、建立AlbumSearch元件 > api及列表寫法可複製AlbumLayout和AlbumPhoto 搜尋框寫法: ``` <input type="text" className="form-control" defaultValue={search} onKeyUp={(e) => { if (e.code === "Enter") { setSearch(e.target.value) }}} /> 筆記:if在判斷當按下Enter後文字才寫入search ``` 列表連結須改為絕對路徑 ``` {list.map((item) => { return <li key={item.id}><Link to={`/album/${item.id}`}> {item.id}</Link></li> })} ``` #### 二、將元件加入App元件中 ``` <Route path='search' element={<AlbumSearch/>} ></Route> 筆記:因為不用回到根目錄,所以path不用加/ ``` #### 三、加入連結 在AlbumLayout中加入AlbumSearch的連結 ``` <p><Link to="search">搜尋頁面</Link></p> ``` --- ### 2.綁定網址參數 #### 一、加入useSearchParams 1.從"react-router-dom"中匯入 2.設定狀態及寫入方法 ``` const [serchParams,setSearchParams]=useSearchParams(); ``` #### 二、搜尋時改用setSearchParams方式寫入 > 原本 > onKeyUp={(e) => { if (e.code === "Enter") { setSearch(e.target.value) 改用setSearchParams ``` onKeyUp={(e) => { if (e.code === "Enter") { setSearchParams({query:e.target.value}) ``` 並加入useEffect,當query值改變重新寫入 ``` useEffect(()=>{ setSearch(searchParams.get("query")) },[searchParams]) ``` 在axios請求前加入條件避免發生錯誤 ``` if(search !== "") 注意:""中間不可有空格 ``` --- ## Navbar樣式優化 ### 1.改用NavLink 將navbar的Link元件改成NavLink元件,當選到該選項時會啟用樣式變化(bootstrap的預設樣式) ![navbar](https://hackmd.io/_uploads/B1Te8MVoye.png) > 可以看見navbar上的Home顏色較另兩個深一些 ### 2.利用判斷isActive的方式設定樣式變化 在每一個NavLink上加入style ``` style={({isActive})=>{ return{ color: isActive ? "red": "", fontSize: isActive ? "20px" :"16px" }}} ``` ![navbar-style](https://hackmd.io/_uploads/SJ2V_fVoye.png) > 選取後的album會較其他兩個選項大一些,及改變顏色 --- ## 將list寫為元件 > album的三個頁面中都有list將其寫為元件,以簡化程式碼 ``` import { Link } from "react-router-dom" function List({list}) { return( <ul> {list.map((item)=>{ return <li key={item.id}><Link to={`/album/${item.id}`}>{item.id}</Link></li> })} </ul>)} export default List 注意:to的連結記得改為絕對路徑,避免路徑錯誤導致畫面無法正常顯示 ``` --- ## notfound畫面,及useNavigate用法 ### 1.notfound畫面的元件 ``` function NotFound() { return( <>這是不存在的頁面,在2秒後會自動跳回主頁</> )} export default NotFound ``` ### 2.加入路徑 在App.jsx中加入 ``` <Route path="*" element={<NotFound/>}></Route> 筆記:*表示所有頁面,當對應不上其他路徑時會導入至*路徑中 ``` ### 3.加入useNavigate ``` import { useNavigate } from "react-router-dom" ``` ``` const navigate = useNavigate() useEffect(()=>{ setTimeout(()=>{ navigate("/") },2000) },[navigate]) 筆記:2秒後自動跳轉到主頁 ``` ## 使用useNavigate撰寫上一頁按鈕 > 用同樣方式導入到需要上一頁按鈕的元件 ``` <button type="button" className="btn-primary mt-2" onClick={()=>navigate(-1)}>回到上一頁</button> 筆記:-1表示回到上一頁 ``` --- ## 程式碼優化 ### 1. 將沒用到的import及console.log移除 ### 2. 將同資料夾下的import統一在一個元件進行管理 #### 一、在pages資料夾下新增index檔 > index要記得小寫 ``` import Home from './Home' import About from './About' import AlbumLayout from './AlbumLayout' import AlbumIndex from './AlbumIndex' import AlbumPhoto from './AlbumPhoto' import AlbumSearch from './AlbumSearch' import NotFound from './NotFound' export { Home, About, AlbumIndex, AlbumLayout, AlbumPhoto, AlbumSearch, NotFound } 注意:要記得放同資料夾的import並且要把./pages/xxx 的/pages移除 ``` #### 二、匯入app元件 ``` import { Home, About, AlbumIndex, AlbumLayout, AlbumPhoto, AlbumSearch, NotFound } from "./pages" ``` 補充:其他匯入方法 > 以Home元件作範例 預設匯出: > 每個元件檔要分開寫 ``` export{default as Home} from "./Home" ``` 具名匯出: ``` export{Home} from "./Home" ``` **整個專案選擇一種方法即可** --- ## 部屬到gh pages 部屬流程可參考 [gh pages部屬筆記 ](https://hackmd.io/7Quer6l2QlqCtnRvAgb0Qw?view#%E6%89%93%E5%8C%85%E4%B8%A6%E9%83%A8%E5%B1%AC%E5%88%B0gh-page) **因為GitHub Pages 不支援 History API,所以要將BrowserRouter改為HashRouter** [gh repo](https://github.com/yuying09/React-router/tree/gh-pages) [gh pages](https://yuying09.github.io/React-router/) --- [更改為plain object router寫法](https://hackmd.io/@jadesnote/BkDyqN4ikg)