今天要和大家介紹的是 SPA,
傳統式的網頁在切換頁面時,都會送出一個 Url 給服務器,之後服務器會依它收到的 Url 判斷要開啟哪個 HTML 檔案,但是這麼做會導致每次換頁的時候都重新載入整個畫面,操作網頁時就常常被等待時間打斷。
而 SPA 又稱為單頁應用程式,至始至終我們的網站內都只有一個 HTML 檔案,
因此已經 Render 過的 Component 便不會再重新載入的等候時間,加快了網站對使用者操作的回饋。
本篇文章內會針對這方面做個簡單的例子實作。
react-router-dom 提供了一些簡單的 Component,讓我們能夠輕鬆 Url 的變化,以及簡單配置對應的 Url 該 Render 哪個 Component,輸入指令安裝:
npm i react-router-dom --save
首先將當前的頁面切成兩個部分,分別是上方的獲取資料區,以及下方的待辦事項區:
既然要切開了,現在是個很好的時候劃分 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
的進入點,將 Content
給 import
然後再 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 會長的很乾淨,像這樣子:
當然,這只是將移出去的地方都砍掉而已,現在我們要將 Content
和 TodoList
在 import
進 src/index.jsx 中。
這時候剛剛在各個 Component 目錄下設置的 index.js 就很有用,因為你不必這麼做:
import Content from './component/Content/Content.jsx';
那太雞肋長了,我們可以很優雅的:
import Content from './component/Content';
因為在 import
的時候如果沒指定檔案,就會預設讀取該目錄下的 index.js。
將 Content
和 TodoList
加入後的完整樣貌會是這樣子:
接下來在 src/index.jsx 中從 react-router-dom
加入 Switch
和 Route
:
import { Switch, Route } from 'react-router-dom';
因為目前 Main
中的 div
沒有用途,因此直接將 div
改成 Switch
,Switch
可以用來組合 Route
,它會依照當前的 Url 尋找符合的 Route
,Render 對應的 Component:
接下來使用 Route
,使用時需要給它兩個 Props,第一個是 path
,也就是需要在什麼 Url 下 Render,第二個是需要被 Render 的 Component:
在預設的情況下,Url 如果為 /
的話那就會顯示 Content
,如果 Url 為 /todolist
的話則是顯示 TodoList
,另外可以注意到我在第一個 Route
內設置了 exact
,這是代表 Url 必須與設定的 path
完全相同,才會 Render。
Link
其實就是超連結,我們可以利用它的屬性 to
來改變 Url,下方將它加進 Main
中,讓 Url 可以在 /
與 /todolist
間切換:
Router 的 Component 都必須設定在最上層的 Component 外,至少要包覆所有使用到 Route 的部分,就像 React-Redux 的 Provider
一樣,但和 Provider
相比我通常會把 Router 放在 Provider
下,而一開始會接觸到的 Router 有兩種:
在說明之前,我們先按照上方說明的,將 BrowserRouter
設置到最外層的 Component 外:
這麼看來一切沒問題了,輸入指令 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
。
在無法使用 BrowserRouter
的另一個選擇為 HashRouter
,讓我們先把 src/index.jsx 中的 BrowserRouter
替換成 HashRouter
:
接著重新執行網頁:
值得注意的是在網址列,我們指定的 Route 都被擋在 /#/
之後,像是跳轉到 todolist
也是:
這時候如果再執行重新整理,也不會出錯,因為它變成由 Hash 在控制,就不會在第一次的時候送出 Url 的對應請求給後端,取得第一次 Render 的資料。
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 的都會有,所以 Content
和 todoList
也都有,只是沒有拿出來而已。
而現在我們要將 List
加入 Route
的設定中,開啟 src/index.jsx :
上方可以注意到,我對 List
所設定 Route
的 path
內容,後方多了個 /:taskName
,其實這個就像 QueryString ,它會自己去對應 Link
的 to
改變 Url 內容的對應位置,記得剛剛上方的 Task
嗎?我對它的每項 Link
指定的 to
為 /list/${task}
,而 ${task}
對應到的位置剛好是 /:taskName
,於是 ${task}
的內容就會被帶進 /:taskName
中。
那該怎麼取得呢?別急,讓我們先運行網站到 todolist
頁面:
接下來點選已經變成超連結的待辦事項名稱,經過 Router 會 Render 出剛剛建立的 List
,而該頁面的內容為 match
:
除了 path
和 url
這些已經知道的屬性,更值得關注的是 params
,發現了嗎?剛剛對應到 /:taskName
的值會被放在這個地方,給予被 Render 的 Component,實務上通常會放入該筆資料的 ID,然後進到頁面後再像後段發送請求,取得完整資料回來呈現。
本文介紹的都是筆者在使用 Router 會用到的幾種功能,其實就繞著那幾種打轉而已,如果想要更深入了解各種技巧,可以參考 官方文檔 ,那文中的範例程式碼也會提供在 GitHub 上,歡迎各位參考:)
其實去年在寫的時候還不曉得 BrowserRouter
和 HashRouter
有什麼不同,每次在打著與去年講述類似主題的文章的時候,都會覺得自己好像又更成長了一點,希望能夠將知識用最簡易的方式傳達給各位:)
如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!
BrowserRouter
在render那段新增BrowserRouter的code少了照片
會需要比對git hub才會知道缺少了甚麼
對 我現在才看到這裡
這裡我在檢查一下 +_+
乍看之下好像又沒什麼問題
根據圖片順序做修改時
在BroserRouter 的地方時
最後一行是長這樣
ReactDom.render(
<Provider store={store}>
<Main />
</Provider>
, document.getElementById('root'));
但是實際應該要是這樣才能正常運作
ReactDom.render(
<Provider store={store}>
<BrowserRouter>
<Main />
</BrowserRouter>
</Provider>
, document.getElementById('root'));
只能說框架真的日新月異
在新版react-router-dom
的Switch
目前被Routes
取代了
提醒一下大家
然後match的部分寫完可以動但是沒辦法顯示
match的內容 不確定是不是因為套件更新的緣故