iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 17
5

前言

今天要和大家介紹的是 SPA,

是一種提升用戶在瀏覽網頁時的技術,

傳統式的網頁在切換頁面時,都會送出一個 Url 給服務器,之後服務器會依它收到的 Url 判斷要開啟哪個 HTML 檔案,但是這麼做會導致每次換頁的時候都重新載入整個畫面,操作網頁時就常常被等待時間打斷。

而 SPA 又稱為單頁應用程式,至始至終我們的網站內都只有一個 HTML 檔案,

但是會針對 Url 的變化,來決定顯示哪個 Component 作為當前頁面,

也就是說, Component 取代了傳統的 HTML 檔案。

因此已經 Render 過的 Component 便不會再重新載入的等候時間,加快了網站對使用者操作的回饋。

本篇文章內會針對這方面做個簡單的例子實作。


前置準備

  1. 文中的專案會以 Day15 的專案架構繼續講解,如果未跟到前一天的進度,可以從 GitHub 上 Clone 下來。
  2. 一顆擁有學習熱忱的心。

使用方法

安裝 react-router-dom

react-router-dom 提供了一些簡單的 Component,讓我們能夠輕鬆 Url 的變化,以及簡單配置對應的 Url 該 Render 哪個 Component,輸入指令安裝:

npm i react-router-dom --save

設置 Route

首先將當前的頁面切成兩個部分,分別是上方的獲取資料區,以及下方的待辦事項區:

既然要切開了,現在是個很好的時候劃分 src/index.jsx 的內容,讓我們在 src 下建立一個 component 資料夾,並在它底下分別建立 TodoList 和 Content 的資料夾,今後如果有新增任何 Component 也就直接放在 component 目錄下統一管理:

|-src
 |-action
 |-component
  |-Content
  |-TodoList
 |-reducer
 |-sagas
 |-store

先處理 Content,來到 src/Content,在目錄下新增 index.js 以及 Content.jsx,接下來把 src/index.jsx 中,有關 Content 的 Component 都複製到 Content.jsx,最後將搬移完成的 Content 匯出:

src/Content/index.js 就作為 Content 的進入點,將 Contentimport 然後再 export,這麼做的用意待會就曉得了:

import Content from './Content';

export default Content;

要注意的是,這裡也許會出現 Webpack 編譯錯誤,因為 Webpack 只預設會去 import js 檔案,如果要加入 jsx,得先到 webpack.config.js 中加入 resolve.extensions 的設置:

webpack.config.js:

module.exports = {
 // 其餘省略
 resolve: { extensions: ['.js', '.jsx'] },
 // 其餘省略
}

另外一部份的 TodoList 也做一樣的事情,將有關 TodoList 的 Component 都從 src/index.jsx 中拆出來,這裡我就不再說明了,讓大家練習看看,如果想確認正不正確可以在文章下方分享的 Github 看今天的程式碼進度。

將 Component 安置好後,src/index.jsx 會長的很乾淨,像這樣子:

當然,這只是將移出去的地方都砍掉而已,現在我們要將 ContentTodoListimport 進 src/index.jsx 中。

這時候剛剛在各個 Component 目錄下設置的 index.js 就很有用,因為你不必這麼做:

import Content from './component/Content/Content.jsx';

那太雞肋長了,我們可以很優雅的:

import Content from './component/Content';

因為在 import 的時候如果沒指定檔案,就會預設讀取該目錄下的 index.js。

ContentTodoList 加入後的完整樣貌會是這樣子:

接下來在 src/index.jsx 中從 react-router-dom 加入 SwitchRoute

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

因為目前 Main 中的 div 沒有用途,因此直接將 div 改成 SwitchSwitch 可以用來組合 Route ,它會依照當前的 Url 尋找符合的 Route,Render 對應的 Component:

接下來使用 Route,使用時需要給它兩個 Props,第一個是 path,也就是需要在什麼 Url 下 Render,第二個是需要被 Render 的 Component:

在預設的情況下,Url 如果為 / 的話那就會顯示 Content,如果 Url 為 /todolist 的話則是顯示 TodoList,另外可以注意到我在第一個 Route 內設置了 exact,這是代表 Url 必須與設定的 path 完全相同,才會 Render。

設置 Link

Link 其實就是超連結,我們可以利用它的屬性 to 來改變 Url,下方將它加進 Main 中,讓 Url 可以在 //todolist 間切換:

設置 Router

Router 的 Component 都必須設定在最上層的 Component 外,至少要包覆所有使用到 Route 的部分,就像 React-Redux 的 Provider 一樣,但和 Provider 相比我通常會把 Router 放在 Provider 下,而一開始會接觸到的 Router 有兩種:

BrowserRouter

在說明之前,我們先按照上方說明的,將 BrowserRouter 設置到最外層的 Component 外:

https://ithelp.ithome.com.tw/upload/images/20200718/20106935UdVCWtzCrE.png

這麼看來一切沒問題了,輸入指令 npm run start 將網站運行,結果如下:

點擊待辦事項後也能夠切換 Url,Render 出 TodoList

這時候請注意,當我們將頁面停留在 /todolist 時,對網頁做重新整理,它卻呈現了 Cannot GET /todolist 的錯誤畫面:

這就是 BrowserRouter 的特點,當然這不是指造成錯誤,讓我們打開 F12 的開發者工具,會發現當我們重新整理的時候,都會送出一個對應的 Url 請求:

這個是在網站擁有後端時,能夠在第一次進到網頁時做 Server Side Render,也就是傳說中的 SSR 伺服器渲染。

這個用意是因為我們的網站都改成 SPA 後,畫面就都變成由前端 Render ,資料也會在 Component 載入完後才會去做請求,也就是在請求回來到 Render 之前,我們的網頁都只會是這樣子:

dist/index.html

<html>
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="./index.css">
    </head>
    <body>
        <div id="root"></div>
        <script src="./bundle.js"></script>
    </body>
</html>

而 SSR 就是為了要再那段空白的過程中,別讓網站什麼都沒有而在第一次載入到網頁時,預先從 Server 載入一個完整的畫面,這麼做不論是對 SEO 或是使用者體驗都相當有幫助。

但是不對啊!

是的,以前端工程師來說,有些作品或是 Side Project 並不會有後端的支援,所以就不適用 BrowserRouter

HashRouter

在無法使用 BrowserRouter 的另一個選擇為 HashRouter ,讓我們先把 src/index.jsx 中的 BrowserRouter 替換成 HashRouter

接著重新執行網頁:

值得注意的是在網址列,我們指定的 Route 都被擋在 /#/ 之後,像是跳轉到 todolist 也是:

這時候如果再執行重新整理,也不會出錯,因為它變成由 Hash 在控制,就不會在第一次的時候送出 Url 的對應請求給後端,取得第一次 Render 的資料。

match 屬性

match 在 Router 的使用中是個很重要的屬性,通常會使用它傳遞一些資料,以下使用 TodoList 的例子講解。

首先打開 src/component/todolist/TodoList.jsx,並對 Task 作出以下修改,讓每個待辦事項都包覆一個 Link,並將 Url 轉至 list/${taskName}

接著在 Component 下建立一個 List 的目錄,我們要建立一個新 Component:

List 中從 Props 內取出的 match 是 Router 在 Render 時放進去的,它會記錄著此次的 Router 內容,包含當前的 url 等等,只要是透過 Route Render 的都會有,所以 ContenttodoList 也都有,只是沒有拿出來而已。

而現在我們要將 List 加入 Route 的設定中,開啟 src/index.jsx :

上方可以注意到,我對 List 所設定 Routepath 內容,後方多了個 /:taskName ,其實這個就像 QueryString ,它會自己去對應 Linkto 改變 Url 內容的對應位置,記得剛剛上方的 Task 嗎?我對它的每項 Link 指定的 to/list/${task} ,而 ${task} 對應到的位置剛好是 /:taskName ,於是 ${task} 的內容就會被帶進 /:taskName 中。

那該怎麼取得呢?別急,讓我們先運行網站到 todolist 頁面:

接下來點選已經變成超連結的待辦事項名稱,經過 Router 會 Render 出剛剛建立的 List ,而該頁面的內容為 match

除了 pathurl 這些已經知道的屬性,更值得關注的是 params,發現了嗎?剛剛對應到 /:taskName 的值會被放在這個地方,給予被 Render 的 Component,實務上通常會放入該筆資料的 ID,然後進到頁面後再像後段發送請求,取得完整資料回來呈現。

本文介紹的都是筆者在使用 Router 會用到的幾種功能,其實就繞著那幾種打轉而已,如果想要更深入了解各種技巧,可以參考 官方文檔 ,那文中的範例程式碼也會提供在 GitHub 上,歡迎各位參考:)


結尾

其實去年在寫的時候還不曉得 BrowserRouterHashRouter 有什麼不同,每次在打著與去年講述類似主題的文章的時候,都會覺得自己好像又更成長了一點,希望能夠將知識用最簡易的方式傳達給各位:)

如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!


上一篇
Day15 | React-Saga 見一次就愛上的 async flows
下一篇
Day17 | 不知道對不對,就把邏輯通通測起來 feat. Jest
系列文
在 React 生態圈內打滾的一年 feat. TypeScript31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

1
連城
iT邦新手 4 級 ‧ 2020-06-09 16:37:45

BrowserRouter
在render那段新增BrowserRouter的code少了照片
會需要比對git hub才會知道缺少了甚麼

對 我現在才看到這裡

神Q超人 iT邦研究生 5 級 ‧ 2020-06-18 14:08:02 檢舉

這裡我在檢查一下 +_+
乍看之下好像又沒什麼問題

連城 iT邦新手 4 級 ‧ 2020-06-19 10:16:18 檢舉

根據圖片順序做修改時
在BroserRouter 的地方時
最後一行是長這樣

ReactDom.render(
    <Provider store={store}>
        <Main />
    </Provider> 
, document.getElementById('root'));

但是實際應該要是這樣才能正常運作

ReactDom.render(
<Provider store={store}>
    <BrowserRouter>
        <Main />
    </BrowserRouter>
</Provider> 
, document.getElementById('root'));
1
ken12462
iT邦新手 5 級 ‧ 2022-06-06 15:23:12

只能說框架真的日新月異
在新版react-router-domSwitch目前被Routes取代了
提醒一下大家

然後match的部分寫完可以動但是沒辦法顯示
match的內容 不確定是不是因為套件更新的緣故

我要留言

立即登入留言