React: React Router DOM v5
React Router DOM v5
安裝指令:
$ npm i react-router-dom@5
Component
Overview
- : Active Router
- : 替component註冊(register) route,只有符合這個route的component才會顯示(類似於state conditionally render)
- ''} >
如果要讓 <Route>
起作用,必須在其外圍包住 <BrowserRouter></BrowserRouter>
,例如
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<BrowserRouter><App/></BrowserRouter>); // *
範例中讓 <BrowserRouter/>
包在整個Component tree的 root component <App/>
外圍,而其 child和decendent components內的 <Route>
都會起作用。
- 若註冊(使用)路由
<Route>
,只有符合路由(route) path 屬性的網址(url)可以渲染相對應的component,也就是被<Router></Router>
包住的component - 使用
<Route>
仍然可以使用一般的component(沒有註冊Router的component);但一般component除非有特別加上內容,否則沒有被註冊的網址只會顯示沒有內容的畫面。 - path prop
- 根路徑
path="/"
- 動態路由(dynamic route)
path="/book-content/:bookId"
:
為動態路由;bookId
為動態路由參數,可以是任意值;- 可以用 useParam 取得路由參數物件,並進一步取得特定路由參數的值,例如
const params = useParam();
,bookId
的值可以用params.bookId
取得
- 根路徑
exact prop
- e.g.,
<Route path="/" exact>
- React-Router v5 預設:多個被
<Route>
包住的components,只要 path 屬性解析後部分符合目前網址(url)就會有多個components同時顯示; exact 屬性讓完全符合(matched) path 屬性的網址才會顯示於畫面,如下的
<Route path="/interests" exact>
只會顯示Interests,不會同時顯示export default function App(){ return (</> <Route path="/home" exact><Home/></Route> <Route path="/about"><About/></Route> <Route path="/interests" exact><Interests/></Route> <Route path="/interests/reading"><Reading/></Route> </>); }
- e.g.,
<Link>
可以用來作導覽列(navigation bar)<Link>
本質上是<a>
標籤<Link to="url">
等同於<a href="url">
- 和
<a>
不同的性質是<a>
如果沒有特別設定,預設會向瀏覽器發送請求(send requests),發送請求後會重新整理(重新渲染)畫面,導致先前畫面的狀態(state)會消失;<a>
可自己手動用event.preventDefault()
解決預設會發送請求的行為;- 但內建的
<Link>
已經處理掉上述<a>
預設會發送請求的行為,並且根據 to 屬性的網址,導向至符合網址的component。
- 其實跟
<Link>
差不多 - 和
<Link>
不同的性質是<Link>
無法用 :active CSS偽類選擇器(pseudo selector)設定連結的 active 樣式- 但
<NavLink activeClassName>
可以用 activeClassName 屬性增加 :active 樣式來設定的連結 active後的樣式
- React-Router v5 的預設是,「只要 path 解析後部分符合目前網址(url)就會有多個components同時顯示」。
<Switch>
可以只顯示一個符合目前網址的component使用方式是將
<Switch>
包在一群<Route>
外圍,如範例:export default function App(){ return (<Switch> <Route path="/home" exact><Home/></Route> <Route path="/about"><About/></Route> <Route path="/interests" exact><Interests/></Route> <Route path="/interests/reading"><Reading/></Route> </Switch>); }
這個範例要注意,若這行
<Route path="/interests" exact>
沒有加上 exact 屬性,則<Route path="/interests/book-list">
的頁面都不顯示:因為在<Switch>
component 內, React-Router v5 只要解析到第一個部分路徑相符**的component就會停止解析其他路徑,並將這個部分路徑符合的component顯示於頁面上。意思是,就算看到 path="/interests/reading",只會解析到 /interests 就會停止解析,並顯示符合 path="/interests" 路徑的
<Interests/>
。
<Redirect>
顧名思義就是讓某個網址重新導向至另一個網址,如範例讓根路徑 <Route path="/">
都導向至首頁 <Home>
:
export default function App(){
return
(<Switch>
<Route path="/" exact>
<Redirect to="/home">
</Route>
<Route path="/home" exact><Home/></Route>
<Route path="/about"><About/></Route>
<Route path="/interests" exact><Interests/></Route>
<Route path="/interests/reading"><Reading/></Route>
</Switch>);
}
結果會是:輸入網址 xxxx-xxxx-xxxx.com 都會導向至 xxxx-xxxx-xxxx.com/home
有時候會需要偵測使用者切換分頁的某些行為並給予一些訊息,這時候可以用 <Prompt/>
設定要顯示訊息框的時機和訊息,如下範例:
export default function Form(){
const [isFormFocused, setIsFormFocused] = useState(false)
const focusForm = () => {
// For prompting message when the form lost focus
setIsFormFocused(true);
}
const submitForm = () => {
// Prevent prompting message when the form is submitted
setIsFormFocused(true);
}
return (<>
<Prompt
when={isFormFocused}
message={'Are you sure leaving this page that lose current form data'}
/>
<form onFocus={focusForm}>
{/* ... */}
<button onClick={submitForm}>Submit</button>
</form>
</>);
}
Hook
- useHistory
- useLocation
- useRouteMatch
- useParams
useHistory
useHistory
hook 會return一個history物件(其實就類似於Web API原生的history物件),這個物件會儲存瀏覽紀錄相關的資訊,並且可以決定要切換到歷史紀錄的分頁或新分頁。
以表單(Form)為例,可以在填完表單後用history物件來導向至新分頁:
import {useHistory} from 'react-router-dom';
export default function Form(){
// ...
const history = useHistory();
const submitForm = () => {
// Prevent prompting message when the form is submitted
setIsFormFocused(true);
history.replace('/'); // *Redirect to root page (homepage)
}
return (<>
<form>
{/* ... */}
<button onClick={submitForm}>Submit</button>
</form>
</>);
useLocation
useLocation
會return一個location物件(也類似於Web API提供的原生location物件),Location物件會顯示目前url的資訊,例如pathname、search等。
Location的資訊可以有很多應用,譬如搭配 [URLSearchParams()](https://developer.chrome.com/blog/urlsearchparams/)
去解析目前網址的參數(query parameters)。假設這是某個搜尋入口網站搜尋dog的網址 https://www.search.com/search?q="dog"
,?
之後的 q="dog"
就是這串網址的參數,然後用 URLSearchParams
去解析參數,會得到一個鍵值(key)為參數名和其參數值的物件:
import { useLocation } from 'react-router-dom';
export default Search(){
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const {q} = queryParams;
return <h1>Current search is {q}</h1>
}
範例中 q
會是網址列上的 "dog"
,而 queryParams
就相當於:
const queryParams = {
q: "dog",
// ...
}
useRouteMatch
useRouteMatch
也會回傳一個物件,物件內容同樣包含前面location物件也有的url,只不過 useRouteMatch
還會提供未解析前的路徑,也就是程式碼中寫的路徑 path(而不是解析過後顯示在網址列的url)。
例如有時候會設定動態路由(dunamic route)來處理不能事先寫入程式碼的路徑,像是範例中包含 :bookId
的路徑 path="/books/:bookId"
就是動態路由:
export default function BookStore(){
return
(<Switch>
<Route path="/books" exact><Books/></Route>
<Route path="/books/:bookId"><BookContent/></Route>
</Switch>);
}
這裡會設定動態路由是因為,書(books)不會只有一本,假設每本書都有自己的編號(bookId),這個動態路由就會根據編號產生相對應的路徑,譬如 bookId
為 1111,而React會按這個編號 1111 產生一個有相應網址 .../books/1111
的 "頁面";書籍編號若為2022則會產生網址為 .../books/2022
的"頁面"。
可以看到動態路由被解析之後的網址會是 .../books/1111
或是 .../books/2022
,並非當初所寫的 .../books/:bookId
;而 useRouteMatch
的path屬性就會提供像 /books/:bookId
這樣實際寫在程式碼的路徑。
import { useRouteMatch } from 'react-router-dom';
export default function BookContent(){
const match = useRouteMatch();
// match.path: "/books/:bookId"
return (
<h1>book id: {match.params.bookId}
<p>path: {match.path}</p>
);
}
useParams
useParams
一樣回傳一個物件,物件內容包含前面所說動態路由的路由參數名稱和其值,以前面舉的 "/books/:bookId"
為例:
const params = useParams();
/*
params = {
bookId: OOOO // OOOO會是真正的bookId值如1111或2022等
}
*/
References React-Router-DOM v5 DOCs React - The Complete Guide (incl Hooks, React Router, Redux) History @MDN Location @MDN URLSearchParams @MDN Easy URL manipulation with URLSearchParams