(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問
本篇內容以react-router v5為主,react-router在v6後有大幅度改變,可參考官方文件或是下方邦友回覆
https://reactrouter.com/en/main/upgrading/v5
這裡的分頁,指的是透過不同的網址(url),導向不同的頁面。
過去我們在設定一個新分頁時,通常是製作一個新的html檔,然後
這種方式,我們稱為後端路由。然而這樣之下,每次換網址時,就要重讀一個檔案,讓頁面整個重繪。但是大多數的時候,我們頁面會有固定的Layout,各個分頁間只有一部分是不一樣的,整個頁面都重新渲染會很浪費效能。
於是,喜歡用前端工程解決問題的工程師又跑出來,讓「前端路由」誕生了。
前端路由的好處就是我們從頭到尾都只需要一個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;
打開terminal,輸入
npm i react-router-dom
首先,請先在App.js的最上方加上
import { HashRouter } from "react-router-dom";
在react-router-dom中,最常使用的是這兩種router介面:
簡單來說,由於#是用來判斷是否要發送request的工具,反言之如果要使用BrowserRouter,server必須要有對應的response(最簡單的方法就是要請後端幫你設定好除了api以外的request都回傳你的index.html,這是個node.js的範例)。因為此系列我們沒有要講後端,所以我們這邊使用HashRouter。
接下來我們每個頁面都是在HashRouter底下,所以請先把App.js下方改為:
const App=()=>{
return(
<HashRouter>
</HashRouter>
);
}
以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/#/
現在我們來設定第二個頁面。請引入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 ,就會看到:
有的時候我們會希望使用者在網址列就要給參數。在route設定
:名稱
,例如以下是要求使用者一定在網址列輸入id的方法,如果沒給id,就不會導向該頁面:
<Route path="/:id" component={SecondPage}/>
:名稱?
,例如以下是讓使用者可以在網址列輸入id的方法,如果沒給id,還是會導向該頁面:
<Route path="/:id?" component={SecondPage}/>
而Route會和你要求相符的參數整理成一個叫match物件中的params屬性,並放在該頁面component的props中。也就是在SecondPage讀取id的方法為:
props.match.params.id
包括剛剛提的match在內,所有關於目前router的一切資訊,都會包成這三個物件,然後放在目前頁面的component內。location跟目前路徑、request者傳來的參數有關,history則是可以讓你呼叫裡面的函數來操作路由器。在以入門為主的這系列就不詳細說明了,可以自己摸索看看。
假設路徑為:
則SecondPage的props:
如果稍微查一下,你會發現有的人會說產生分頁要用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-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
謝謝這系列的教學,講解得很清楚!
如同前面網友留言,目前有幾個變動:
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>