iT邦幫忙

2021 iThome 鐵人賽

DAY 25
1
Modern Web

不只懂 Vue 語法:Vue.js 觀念篇系列 第 25

不只懂 Vue 語法:試解釋 hash 與 history 模式的分別? 為何 history 模式會回傳 404?

問題回答

Vue 預設是使用 hash 模式,但可選擇使用 history 模式。hash 模式時的 URL 會帶 # 符號,例如 https://example.com/#/about 跳轉時不會發出 HTTP 請求,只會按 # 後的內容來顯示相應的元件。

history 模式不會帶 #,例如 https://example.com/about。跳轉頁面時會發 HTTP 請求。背後原理是使用 Web History API 和 建構 Router 來避免重刷頁面。但注意,history 模式需配合後端設定,把找不到的路由全都指向 index.html,才不會出現 404 的問題。

以下會再作詳細解說。

hash 模式是如何運作?

  • Vue router 是使用 hashchange 事件來監聽 URL 上 # 後內容的變化。
  • 利用了瀏覽器的錨點功能,每次跳轉頁面時,在瀏覽器上都會有記錄,因此可以按「上一頁」來回到前一頁。
  • 除了第一次載入網站後,伺服器只會接收到 # 前的路由,並把首頁回傳給你,之後所有跳轉頁面都不會發出 HTTP 請求。所以使用者其實是一直停留在 index.html,並在此檔案上切換顯示不同元件來模擬跳轉頁面。
  • 兼容性會比 history 模式好,因為 history 模式是依賴 HTML5 的 Web History API,特定瀏覽器才會支援。

hash 模式的問題

  • URL 的外觀上可能不太好看,因為夾帶了 # 符號。
  • 不利 SEO,爬蟲不會讀取到 # 後的內容,因為會把 # 當作是錨點。
  • 會與網站裏的錨點功能產生衝突。錨點功能例如:
<a href="#about"> About Us </a>
<h2 id="about">
    ...
</h2>    

history 模式是如何運作?

  • 透過 HTML5 history API 裏的 pushState()replaceState() 方法,以及瀏覽器的 popstate 事件來實現。當 URL 有變,就會 window.location.pathname 來取出路由內容,再在建構 router 物件時,利用 route 的物件把該 pathname 值對應到指定的元件。過程不會重刷頁面。
  • 過程例如使用 pushState() 更換 URL、存入瀏覽記錄等。

history 模式的問題

  • 要考慮瀏覽器兼容性。
  • History 模式會觸發 HTTP 請求。因為 Vue 使用了以上提到的 HTML history API,避免了重刷頁面。
  • 例如,直接訪問https://www.example.com/product/111,會出現 404 頁面,因為伺服器會去查看有沒有 product/111 這個路由來處理這個 HTTP 請求,最後因為沒找到所以就回 404頁面。

以 Node.js 的 Express.js 框架為例。個人對後端的認識不多,只能以自己過往寫 Express.js 來設定伺服器作例子,讓我們更清楚明白以上提到伺服器找不到路由的意思。

先看看 Express.js 最簡單的起手式:

app.js

var express = require('express');
var app = express();

// URL:http://localhost:3000
app.get('/', function(req,res){
    res.send('首頁');
})

// URL:http://localhost:3000/user
app.get('/user', function(req,res){
    res.send('個人頁面');
})

// 如果沒有對應到以上任何一個 URL,最後就會執行以下程式碼
// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

如上做法,跟前端設定 404 頁面的原理是一樣。程式碼由上到下執行,當 HTTP 請求進來時,如果在這裏找不到此請求的 URL,最後就會執行回傳 404 錯誤的程式碼。

因此,後端伺服器需要設定,如果沒找到對應的路由,就一律回傳 index.html。注意,雖然有發出 http 請求,但是以上提過,Vue 是使用 HTML5 history API 以及建構 route 物件的方法,避免了重刷頁面,並實現切換頁面的效果。因此跳轉頁面時不會重刷頁面。

到底使用 history 模式時,回傳 404 的實際情況是長怎樣?

以上講了很多原理,但實際上回傳 404 的情況是怎樣?以下將示範一次,使用 history 模式時,到底如何會發生 404 問題。

首先使用 Vue CLI 建立一個 Vue 專案,並選擇包含 router 功能,router 使用 history mode。

第一步:用 live server 打開 dist 裏的 index.html

之後打開檔案,輸入 npm run build 打包一次專案,產生 dist 資料夾。把 dist 資料夾拖到 VScode 裏打開,並打開 index.html,再使用 live server 瀏覽這個檔案,你會發現剛載入網頁後,URL 是 http://127.0.0.1:5500/index.html,但內容是空的:

那是因為現在是直接用 live serve 瀏覽 index.html 檔案。如果你直接訪問 http://127.0.0.1:5500/ 就會成功顯示:

第二步:進入 About 及 Home 頁面

然後,嘗試按下 About 按鈕,發現 URL 變成http://127.0.0.1:5500/about,有成功渲染 about 元件:

再按 Home 按鈕,也有成功渲染 Home 元件,URL 變成 http://127.0.0.1:5500/

第三步:直按訪問 http://127.0.0.1:5500/about

但當你試著直接訪問 http://127.0.0.1:5500/about 此 URL,會出現 404 頁面:

或者,你可以試試在第二步,成功打開 About 頁面時,再重刷頁面,也同樣是會出現 404 頁面。

現在我們逐一理解原因。

第一步,伺服器收到 / 的請求時,會正常指向 index.html,並讓 Vue 使用 Web History API 和建構 Router 物件來把 pathname 與指定元件對應起來。

第三步,當你直接訪問 http://127.0.0.1:5500/about,就會向後端伺服器發出 /about 這個 GET 請求,但是伺服器沒有設定處理 /about 這路由,因此回傳 404。

那為什麼在第 2 步,可以成功打開 About 頁?個人理解是,因為我們不是直接訪問 About頁,而是透過打開了首頁,再按 Nav bar 的 About 來進入 About 頁。因此,這裏的流程是:你先載入了 index.html,之後就開始使用 Web History API,跑以上提過的流程,把 pathname 對應到指定元件,成功把 About 元件顯示出來。

從以上情況得知,要避免以上 404 的情況,在伺服器就需要把所有找不到的 URL 一律指向 index.html。例如,當你直接訪問 http://127.0.0.1:5500/about 時,就會指向 index.html,然後 Vue 就會跑 Web history API,由 index.html 裏的 Vue router 去把 pathname 對應到指定元件。

總結

  • hash 模式下的 URL 會帶有 #,跳轉時不會發出 HTTP 請求。限制是不利 SEO,以及會與錨點功能有衝突等。
  • history 模式不會有 #,跳轉時會發出 HTTP 請求。限制是瀏覽器兼容性較差,以及後端需要自行設定,避免 404 問題。

參考資料

原来这就是hash模式和history模式的区别(vue-router mode)
Using Vue Router History Mode


上一篇
不只懂 Vue 語法:試解釋如何使用導航守衛?
下一篇
不只懂 Vue 語法:試解釋嵌套路由與嵌套命名視圖的概念?
系列文
不只懂 Vue 語法:Vue.js 觀念篇31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言