不只懂 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


<<:  Day 19: Behavioral patterns - Command

>>:  30天学会 Python: Day 18-try try 看

Day15 - 在 Next.js 做 JWT 验证,使用既有的 Backend API - PART 1

NextAuth + JWT authentication 虽然 Next.js 的定位是一个全端框...

Day33. 使用RSpec写测试

今天会针对一个services进行单元测试,并详述过程 config 首先先介绍基本的测试所安装的g...

Day18:今天我们来谈一下如何使用ShellPhish工具进行社交工程演练

Social engineering可以采取多种形式,包括网络钓鱼电子邮件,假冒网站 及冒名顶替。 ...

认识与建立CSS样式表(DAY7)

在第一篇介绍时提到Html就像礼物的实体,而我们现在要认识的CSS样式表就像是礼物的外包装,要如何包...