iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 27
2
Modern Web

給初入JS框架新手的React.js入門系列 第 27

【React.js入門 - 27】 我要更多更多的分頁 - react-router-dom (上)

  • 分享至 

  • xImage
  •  

(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問


本篇內容以react-router v5為主,react-router在v6後有大幅度改變,可參考官方文件或是下方邦友回覆
https://reactrouter.com/en/main/upgrading/v5

這裡的分頁,指的是透過不同的網址(url),導向不同的頁面。

過去我們在設定一個新分頁時,通常是製作一個新的html檔,然後

  1. 放在server的public_html底下,讓使用者根據檔案路徑去存取。
  2. 從後端程式碼去監聽不同url該response哪個靜態檔案

這種方式,我們稱為後端路由。然而這樣之下,每次換網址時,就要重讀一個檔案,讓頁面整個重繪。但是大多數的時候,我們頁面會有固定的Layout,各個分頁間只有一部分是不一樣的,整個頁面都重新渲染會很浪費效能。

於是,喜歡用前端工程解決問題的工程師又跑出來,讓「前端路由」誕生了。

認識名詞 - SPA

前端路由的好處就是我們從頭到尾都只需要一個index.html,換路徑時只會更改頁面裡不一樣的地方。無論是對於使用者還是開發者而言,看起來不像是使用/開發多個頁面,而像是一個應用程式。這樣的網頁程式,我們就稱之為Single Page Application(SPA)。

在這邊我們會把焦點放在如何使用React中幫助實現前端路由的插件react-router-dom,有關前端路由的原理不會去探討,有興趣的可以參考這篇

準備 - 用來當分頁的元件

請先建立一個FirstPage.js和SecondPage.js,等等我們會用這兩個元件當作分頁。
FirstPage.js

import React from 'react';

const FirstPage=()=>{
    const StyleSheet={
        width:"100vw",
        height:"100vh",
        backgroundColor:"#FF2E63",
        display: "flex",
        alignItems:"center",
        justifyContent:"center",
        flexDirection:"column"
    }
    return(
        <div style={StyleSheet}>
            <h1 style={{color:"white",fontFamily:"Microsoft JhengHei"}}>我是第一頁</h1>
        </div>
    )
}

export default FirstPage;

SecondPage.js

import React from 'react';

const SecondPage=()=>{
    const StyleSheet={
        width:"100vw",
        height:"100vh",
        backgroundColor:"#08D9D6",
        display: "flex",
        alignItems:"center",
        justifyContent:"center",
        flexDirection:"column"
    }
    return(
        <div style={StyleSheet}>
            <h1 style={{color:"white",fontFamily:"Microsoft JhengHei"}}>我是第二頁</h1>
        </div>
    )
}

export default SecondPage;

安裝react-router-dom

打開terminal,輸入

npm i react-router-dom

HashRouter v.s BrowserRouter

首先,請先在App.js的最上方加上

import { HashRouter } from "react-router-dom";

在react-router-dom中,最常使用的是這兩種router介面:

  1. HashRouter: 頁面路徑最前面會有個「#」,換url時不會發送request。
  2. BrowserRouter: 頁面路徑不會有井字,但換url時會發送request。

簡單來說,由於#是用來判斷是否要發送request的工具,反言之如果要使用BrowserRouter,server必須要有對應的response(最簡單的方法就是要請後端幫你設定好除了api以外的request都回傳你的index.html,這是個node.js的範例)。因為此系列我們沒有要講後端,所以我們這邊使用HashRouter。

接下來我們每個頁面都是在HashRouter底下,所以請先把App.js下方改為:

const App=()=>{
    return( 
        <HashRouter>

        </HashRouter>
    );
}

用Route設定第一個頁面

以Router(路由器)導向Route(路由)

在react-router-dom中,我們也是要把route先放在router中,再於route設定頁面,才能讓router來導向對應位置的網頁。請在App.js剛剛import的地方多引入Route:

import {HashRouter,Route} from "react-router-dom";

Route的基本使用方法為

<Route path="路徑" component={ 用來當頁面的元件名稱(不是標籤) } />

所以現在我們先把剛剛做好的FirstPage.js引入

import FirstPage from "./FirstPage";

然後要把route先放在router中,再於route設定頁面為FirstPage.js

import React from 'react';
import {HashRouter,Route} from "react-router-dom";
import FirstPage from "./FirstPage";

const App=()=>{
    return( 
        <HashRouter>
            <Route path="/" component={FirstPage}/>
        </HashRouter>
    );
}

export default App;

開啟http://localhost:3000/#/

用 Switch + Route 設定第二個頁面

現在我們來設定第二個頁面。請引入SecondPage.js

import SecondPage from "./SecondPage";

然後在router底下多加入一個設定SecondPage.js為頁面的route

import React from 'react';
import {HashRouter,Route} from "react-router-dom";
import FirstPage from "./FirstPage";
import SecondPage from "./SecondPage";

const App=()=>{
    return( 
        <HashRouter>
            <Route path="/" component={FirstPage}/>
            <Route path="/second" component={SecondPage}/>
        </HashRouter>
    );
}

export default App;

雖然這樣是可以運作的,但是一般會在所有的route外面包一個東西叫Switch,它可以幫助我們找到目前路徑所正確應該對應到的Route、正確的傳遞參數。
請引入Switch:

import {HashRouter,Route,Switch} from "react-router-dom";

然後把它包在所有的Route外面:

import React from 'react';
import {HashRouter,Route,Switch} from "react-router-dom";
import FirstPage from "./FirstPage";
import SecondPage from "./SecondPage";

const App=()=>{
    return( 
        <HashRouter>
            <Switch>
                <Route path="/" component={FirstPage}/>
                <Route path="/second" component={SecondPage}/>
            </Switch>
        </HashRouter>
    );
}
export default App;

接著執行之後輸入不同的網址,你就會發現

哇! 不管我輸 http://localhost:3000/#/ 還是 http://localhost:3000/#/second ,出現的都是firstPage耶

這是因為Route只要偵測到目前的path「有包含你設定的字串」就會顯示該頁面,當多個route符合時,Switch會導向第一個符合的Route,所以你在http://localhost:3000/#/second 的時候實際上是顯示firstPage。

不過這不是現在我們想要的狀況,請在firstPage的Route的props當中加上exact:

<Route exact path="/" component={FirstPage}/>

這個props會讓Route必須偵測到目前路徑完全符合path中的字串時才會顯示該頁面。
現在進入http://localhost:3000/#/second ,就會看到:

設定url參數

有的時候我們會希望使用者在網址列就要給參數。在route設定

  1. 要求一定要輸入參數的方法是在path上寫:名稱,例如以下是要求使用者一定在網址列輸入id的方法,如果沒給id,就不會導向該頁面:
    <Route path="/:id" component={SecondPage}/>
    
  2. 不強迫輸入參數的方法是在path上寫:名稱?,例如以下是讓使用者可以在網址列輸入id的方法,如果沒給id,還是會導向該頁面:
    <Route path="/:id?" component={SecondPage}/>
    

而Route會和你要求相符的參數整理成一個叫match物件中的params屬性,並放在該頁面component的props中。也就是在SecondPage讀取id的方法為:

props.match.params.id

match & location & history

包括剛剛提的match在內,所有關於目前router的一切資訊,都會包成這三個物件,然後放在目前頁面的component內。location跟目前路徑、request者傳來的參數有關,history則是可以讓你呼叫裡面的函數來操作路由器。在以入門為主的這系列就不詳細說明了,可以自己摸索看看。

假設路徑為:

則SecondPage的props:

小結 - react router v.s react router dom

如果稍微查一下,你會發現有的人會說產生分頁要用react-router、有的人會說要用react-router-dom,那這兩個有什麼差別呢?

其實react-router-dom就只是以react-router為基礎,再加上一些讓你可以藉由操作DOM來改變路由的功能。所以當我們安裝完react-router-dom,react-router就同時被包在裡面了。我們不需要再執行npm i react-router

此篇是基礎用react-router-dom產生分頁的方法,下一篇會來講react-router-dom提供的一個元件Link的使用,以及常見架構配合react-router-dom的實踐方法。


上一篇
【React.js入門 - 26】 input使用、input與state的互動 (控制組件) 、其他輸入元素
下一篇
【React.js入門 - 28】 我要更多更多的分頁 - react-router-dom (下)
系列文
給初入JS框架新手的React.js入門31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

1
kata_1219
iT邦新手 5 級 ‧ 2022-04-20 05:29:05

react-router-dom 第六版以後有做一些更動
像是要<route>要放進<routes>中及component改為element
https://stackoverflow.com/questions/69832748/error-error-a-route-is-only-ever-to-be-used-as-the-child-of-routes-element

謝謝這系列的教學,講解得很清楚!

0
AndrewYEE
iT邦新手 3 級 ‧ 2023-01-31 19:09:35

如同前面網友留言,目前有幾個變動:
1.官方已經不建議使用HashRouter link

We strongly recommend you do not use HashRouter unless you absolutely have to.

2.在react-router-dom v6版本中做出以下改動:

  • Switch被Routes取代。
    
  • Route中component關鍵字改成element,並且需要以引入Component的方式引入,以避免與一般function混淆。
    

舊版:

<BrowserRouter>
    <Switch>
      <Route exact path="/" component={Home}/>
      <Route exact path="/login" component={Login}/>
      <Route exact path="/recovery-password" component={RecoveryPassword}/>
      <Route path="*" component={NotFound}/>
    </Switch>
</BrowserRouter>

新版:

<BrowserRouter>
    <Routes>
      <Route exact path="/" element={<Home/>}/>
      <Route exact path="/login" element={<Login/>}/>
      <Route exact path="/recovery-password" element={<RecoveryPassword/>}/>
      <Route path="*" element={<NotFound/>}/>
    </Routes>
</BrowserRouter>

link

我要留言

立即登入留言