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>
        </>);
      
      }
      


  • <Link> 可以用來作導覽列(navigation bar)
  • <Link> 本質上是 <a> 標籤
    • <Link to="url"> 等同於 <a href="url">
  • <a> 不同的性質是
    1. <a> 如果沒有特別設定,預設會向瀏覽器發送請求(send requests),發送請求後會重新整理(重新渲染)畫面,導致先前畫面的狀態(state)會消失;
    2. <a> 可自己手動用 event.preventDefault() 解決預設會發送請求的行為;
    3. 但內建的 <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