# 使用 React Router 建立一個 SPA(Single-Page App) 頁面 ###### tags: `ReactJS` `Javascript` ## 什麼是 SPA SPA 英文直譯就是單一頁面應用程式,顧名思義就是網頁不做轉跳頁面的動作就可以達到新增、刪除、修改等功能,例如 AJAX 就是 SPA 的一種。 SPA 的優點是所有資源不需要重新載入,只針對需要更新修改的部分資料做處理。如此可減少不必要的資源浪費。 ![](https://i.imgur.com/lMQc9YN.jpg) ## 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 目前位置 ![](https://i.imgur.com/girSOwd.gif) ### 建立專案 開啟終端機並輸入下列指令建立專案,`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 ``` ![](https://i.imgur.com/WEOpbT3.png) 因為修改它的範例檔案實在太麻煩了,我們直接刪除 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內: ![](https://i.imgur.com/kAlqY6Y.png) ### 建立內容頁面 再來我們在 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 的狀態。 ![](https://i.imgur.com/V3L6O6E.gif ) 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> ``` 至此我們的網站好像已經完成了,但還是有一個小地方看起來怪怪的,就是網址列的路徑竟然多了一個 `#`。 ![](https://i.imgur.com/CmnBnJH.gif) 這是因為我們使用的是 `<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) :::