# 使用 React Router 建立一個 SPA(Single-Page App) 頁面
###### tags: `ReactJS` `Javascript`
## 什麼是 SPA
SPA 英文直譯就是單一頁面應用程式,顧名思義就是網頁不做轉跳頁面的動作就可以達到新增、刪除、修改等功能,例如 AJAX 就是 SPA 的一種。 SPA 的優點是所有資源不需要重新載入,只針對需要更新修改的部分資料做處理。如此可減少不必要的資源浪費。

## React Router
在 google ReactJs 的 router 時,會一直交叉出現三種很相像的關鍵字 `react-router` `react-router-dom` `react-router-native` ,我們先初步了解一下這三項的不同:
| Library | 應用 | 說明 |
|--- | :---: | --- |
| react-router | 路由核心 | 實現路由的核心功能,但沒有提供操作 dom 進行跳轉的API。 |
| react-router-dom | WEB | 基於 `react-router` 加入可操作 dom 進行跳轉的API。 |
| react-router-native | APP | 基於 `react-router`,類似 `react-router-dom`,並加入了 `react-native` 的功能。|
## 實作
這次練習的目標是建立一個韓國女子團體[臉紅思春期](https://zh.wikipedia.org/wiki/%E8%87%89%E7%B4%85%E7%9A%84%E6%80%9D%E6%98%A5%E6%9C%9F)的介紹網站。預期的效果如下:
1. 換頁時不會整個頁面轉跳,只更新右邊的內容
2. 網址列的位置會根據選擇的內容變動
3. 可使用瀏覽器本身的返回上一頁、下一頁功能
4. 左邊選單會根據右邊內容 highlight 目前位置

### 建立專案
開啟終端機並輸入下列指令建立專案,`hello_react` 為專案名稱,可自行隨意命名。
```
create-react-app hello_react
```
建立完成後輸入以下指令,進入該資料夾
```
cd hello_react
```
### 安裝 react-router
由於我們現在要建立的是一個網頁,較適合使用 `react-router-dom` ,且因為 `react-router-dom` 是基於 `react-router` 的 library,因此不需再另外安裝 `react-router`。
>我有使用 yarn 管理 js,沒有安裝 yarn 的人使用 npm 的安裝指令就可以了。
#### npm 安裝指令
```
npm i react-router-dom --save
```
#### yarn 安裝指令
```
yarn add react-router-dom
```
### 安裝 node-sass
如果想使用 SASS/SCSS/LESS 編譯 CSS 的話,則需再安裝 node-sass,不用的人直接跳過這個步驟就可以囉。
#### npm 安裝指令
```
npm install node-sass
```
#### yarn 安裝指令
```
yarn add node-sass
```
### 專案初始化
安裝完成後,hello_react 資夾內就會有一包初始的專案,輸入以下指令就可以看到預設的範例頁面。
```
npm run start
```

因為修改它的範例檔案實在太麻煩了,我們直接刪除 public 及 src 資料夾內的所有檔案。並在 public 資料夾內建立一個 ==index.html== 內容如下:
```htmlembedded=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
```
然後在 src 資料夾內建立一個 ==index.js== 並加入以下內容用來渲染出我們的網頁。
```javascript=
import React from "react";
import ReactDOM from "react-dom";
import Main from "./Main"; //Main元件內會放網站的主要內容
require(`./stylesheets/app.scss`); //網站樣式
ReactDOM.render(
<Main/>,
document.getElementById("root")
);
```
### 建立網站框架
建完 index.js 後,再於 src 資料夾內建立一個 ==Main.js==,本次範例主要的 html 框架會放在這支 js 裡。
```javascript=
import React, { Component } from "react";
class Main extends Component {
render() {
return (
<div className="app">
<div className="main-wrap">
//左邊選單
<nav className="nav">
<h1 className="logo">BOL4<img src="/img/bo4_logo.jpg" alt="BOL4"/></h1>
<ul className="nav-list">
<li><a href='/'>BOL4</a></li>
<li><a href='/Member'>Member</a></li>
<li><a href='/Album'>Album</a></li>
</ul>
</nav>
//右邊內容
<main className="main">
</main>
</div>
</div>
);
}
}
export default Main;
```
建立完成後的畫面如下,CSS的部分就不帶步驟了,圖檔放在public/img內:

### 建立內容頁面
再來我們在 src 內建立一個資料夾 ==page== 用來放我們右邊內容的頁面,對照左邊選單檔名及內容如下:
* BOL4:==Home.js==
```javascript=
import React, { Component } from "react";
class Home extends Component {
render() {
return (
<div>
<div className="profile-img"></div>
<h2>臉紅的思春期 Bolbbalgan4</h2>
<p>臉紅的思春期(韓語:볼빨간 사춘기,英語:Bolbbalgan4 或 BOL4),韓國雙人女子組合。由安智煐及禹智潤組成。因出道專輯《Full Album RED PLANET》中一曲《給你宇宙》逆襲韓國的各個音樂排行榜而為大眾所認識。兩人曾於2014年以《慶北 榮州 鄉村樂隊 臉紅的思春期》之名組成的四人樂隊,參加Mnet媒體選秀節目《Super Star K6》,後在10強止步。</p>
<small><a href="https://zh.wikipedia.org/wiki/%E8%87%89%E7%B4%85%E7%9A%84%E6%80%9D%E6%98%A5%E6%9C%9F" target="_blank" rel="noopener noreferrer">資料來源:Wiki</a></small>
</div>
);
}
}
export default Home;
```
* Member:==Member.js==
```javascript=
import React, { Component } from "react";
class Member extends Component {
render() {
return (
<div>
<h2>Member</h2>
<ul className="member-list">
<li>
<h3>安智煐 안지영 - An Ji Yeong</h3>
1995年9月14日 生於韓國慶尚北道榮州市<br/>
隊長、主唱、主作詞曲
</li>
<li>
<h3>禹智潤 우지윤 - Woo Ji Yun</h3>
1996年1月6日 生於韓國慶尚北道榮州市<br/>
副唱、RAP、主領舞、吉他、貝斯
</li>
</ul>
<small><a href="https://zh.wikipedia.org/wiki/%E8%87%89%E7%B4%85%E7%9A%84%E6%80%9D%E6%98%A5%E6%9C%9F" target="_blank" rel="noopener noreferrer">資料來源:Wiki</a></small>
</div>
);
}
}
export default Member;
```
* Album:==Album.js==
```javascript=
import React, { Component } from "react";
class Album extends Component {
render() {
return (
<div>
<h2>Album</h2>
<ul className="album-list">
<li>
<img src="/img/album1.jpg" alt=""/>
<h3>RED PLANET 'Hidden Track'</h3>
</li>
<li>
<img src="/img/album2.jpg" alt=""/>
<h3>RED PLANET</h3>
</li>
<li>
<img src="/img/album3.jpg" alt=""/>
<h3>RED ICKLE</h3>
</li>
</ul>
<small><a href="https://zh.wikipedia.org/wiki/%E8%87%89%E7%B4%85%E7%9A%84%E6%80%9D%E6%98%A5%E6%9C%9F" target="_blank" rel="noopener noreferrer">資料來源:Wiki</a></small>
</div>
);
}
}
export default Album;
```
### 使用 React Router 串連頁面
建立好框架跟每個頁面,就可以來使用 react router 囉!首先我們回到 ==Main.js== 上方引入 react-router-dom 及三個分頁
```javascript=
import { Route, NavLink, HashRouter } from "react-router-dom";
import Home from "./page/Home";
import Member from "./page/Member";
import Album from "./page/Album";
```
接著我們使用 ==<HashRouter></HashRouter>== 定義我們「路由區域」,路由區域的範圍須包含導航列及要呈現的內容區域。由於程式太長,以下我們只顯示修改範圍的程式。
```javascript=
render() {
return (
<div className="app">
<div className="main-wrap">
<HashRouter>
<nav className="nav">
<h1 className="logo">BOL4<img src="/img/bo4_logo.jpg" alt="BOL4"/></h1>
<ul className="nav-list">
<li><a href='/'>BOL4</a></li>
<li><a href='/Member'>Member</a></li>
<li><a href='/Album'>Album</a></li>
</ul>
</nav>
<main className="main">
</main>
</HashRouter>
</div>
</div>
);
}
```
HashRouter 有提供 `NavLink` 這個 API,讓我們可以定義 `to` 屬性前往我們的指定路徑,由於 `NavLink` 會自動產生 `<a>` 標籤,因此我們直接把它取代 `<a>` 即可,修改後的程式如下:
:::warning
注意:此處 `to` 的路徑並非檔案路徑,而是自訂的頁面路徑
:::
```javascript=
<ul className="nav-list">
<li><NavLink to='/'>BOL4</NavLink></li>
<li><NavLink to='/Member'>Member</NavLink></li>
<li><NavLink to='/Album'>Album</NavLink></li>
</ul>
```
接著我們使用`<Route>`來定義指定的路徑是要指向哪個元件,呈現在哪個位置。
```javascript=
<main className="main">
<Route path="/" component={Home}/>
<Route path="/Member" component={Member}/>
<Route path="/Album" component={Album}/>
</main>
```
到這邊我們 `npm run start` 啟動專案,可以看到目前已經能成功切換內容了,且被啟動的項目會自動加上 ==class="active"==。但第一個 Home 頁面卻不會隨著切換消失一直呈現 active 的狀態。

Home 之所以會一直呈現 active 狀態是因為我們定義 Home 的路徑為 `/`,而 Member 跟 Album 的路徑也都含有 `/`,所以程式在比對時 Home 才總是 active 的狀態,為了避免這種狀況我們可以使用 `exact` 屬性,更加精準的定義我們當前所在的頁面。
```javascript=
<main className="main">
<Route exact path="/" component={Home}/>
<Route path="/Member" component={Member}/>
<Route path="/Album" component={Album}/>
</main>
```
除了 <Route> 以外,<NavLink> 也需加上 `exact` 避免有兩個 active 樣式。
```javascript=
<ul className="nav-list">
<li><NavLink exact to='/'>BOL4</NavLink></li>
<li><NavLink to='/Member'>Member</NavLink></li>
<li><NavLink to='/Album'>Album</NavLink></li>
</ul>
```
至此我們的網站好像已經完成了,但還是有一個小地方看起來怪怪的,就是網址列的路徑竟然多了一個 `#`。

這是因為我們使用的是 `<HashRouter>`,簡單解釋 Router 的兩種方式:
| API | 原理 | 情境 |
| ------- | ------- | ------- |
| HashRouter | hash | 當作同頁面的錨點 |
| BrowserRouter | HTML5 history API | 頁面在其他目錄時使用 |
因此可知,這個範例其實較適合使用的是 BrowserRouter,我們只需要將 HashRouter 換成 BrowserRouter 就完成我們這次的範例囉。
```javascript=
import { Route, NavLink, BrowserRouter } from "react-router-dom";
```
```javascript=
<BrowserRouter>
<nav className="nav">
<h1 className="logo">BOL4<img src="/img/bo4_logo.jpg" alt="BOL4"/></h1>
<ul className="nav-list">
<li><NavLink exact to='/'>BOL4</NavLink></li>
<li><NavLink to='/Member'>Member</NavLink></li>
<li><NavLink to='/Album'>Album</NavLink></li>
</ul>
</nav>
<main className="main">
<Route exact path="/" component={Home}/>
<Route path="/Member" component={Member}/>
<Route path="/Album" component={Album}/>
</main>
</BrowserRouter>
```
## 參考來源
:::info
* [React Router: Declarative Routing for React.js](https://reacttraining.com/react-router/web/guides/philosophy)
* [Creating a Single-Page App in React using React Router](https://www.kirupa.com/react/creating_single_page_app_react_using_react_router.htm)
* [一探究竟了解React-router 4簡易教學](https://www.ucamc.com/e-learning/javascript/278)
* [[译]React Router:从V2/V3迁移到V4](https://github.com/YutHelloWorld/Blog/issues/4)
:::