彻底弄懂hash/history模式的区别及两种模式在vue-router中的坑

2,245 阅读3分钟

一、什么是hash/history模式

对于vue这类渐进式前端开发框架,为了构建 SPA(单页面应用),需要引入前端路由系统,即Vue-Router 。前端路由的核心在于改变视图的同时不会向后端发出请求。

为了实现改变视图不会向后端发送请求,浏览器提供了hash及history两种模式。

  • hash模式:url中会有#存在如www.baidu.com/#/a,看起来丑陋且不好做SEO,但是由于路由器向服务器发请求的时候会忽略#后面的值,所以在浏览器刷新的时候都是很正常的不会再次发请求。
  • history模式:利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。但当没有后端进行相应配置时,url就是我们看的正常的路由样子,就是因为看起来很像真的,所以刷新浏览器的时候(浏览器向服务器请求)会发现服务器根本没有这个路径资源,所以返回404。


二、history模式导致的问题

1、刷新浏览器返回404

// routes.js

export default [
  {
    path: '/',
    redirect: '/app'
  },
  {
    path: '/app',
    component: Todo
  },
  {
    path: '/login',
    component: Login
  }

打开浏览器访问http://localhost:8000/,会自动跳转到http://localhost:8000/app

当直接输入http://localhost:8000/login或者http://localhost:8000/app 会返回404

原因:

解决方法:用webpack对devServer进行配置

// 当vue-router采用history模式时,必须有后端进行配置,否则当页面刷新时会出现404,
// 这是因为后端不认识当前刷新的路由,找不到对应的路径,所以会返回404,
// 在开发环境下为了解决这个问题可以进行historyApiFallback配置使得任意的 404 响应都可能需要被替代为 index.html。
// 若在webpack的output配置中有配置到publicPath: '/public/',则这里应该对应配置成index: '/public/index.html'
  historyApiFallback: {
    index: '/index.html'
  },

2、vue-router嵌套路由404

官方文档提示当使用嵌套路由时要注意,以 / 开头的嵌套路径会被当作根路径。

// routes.js

export default [
  {
    path: '/',
    redirect: '/app'
  },
  {
    path: '/app',
    component: Todo,
    name: 'app',
    meta: {
      title: 'this is app',
      description: 'app'
    },
+    children: [
+      {
+        path: 'test',
+        component: Login
+     }
+    ]
  },
  {
    path: '/login',
    component: Login
  }
]

hash模式下bundle.js请求路径:


history模式下bundle.js请求路径:


于是我们可以发现问题所在,如图所示,webpack帮我们打包js代码后自动在index.html引入方式为src="bundle.js",即相对路径./bundle.js 。


而根据官方文档所述,以 / 开头的嵌套路径会被当作根路径。因此:

原因:

  • history模式——此时的根路径为localhost:8000/app,相对路径使得请求当前根路径下的bundle.js资源,而服务端找不到该资源于是返回404
  • hash模式——此时的根路径他同样为/app,但由于向服务器发请求的时候会忽略#后面的值,因此不会发送localhost:8000/#/app/bundle.js而仅仅发送localhost:8000/的请求,此时相对路径使得请求当前路径下的bundle.js资源,请求成功,再由前端路由处理/app/test

解决方式:webpack配置output使得请求bundle.js的路径是绝对路径而不是相对路径

output: {
    filename: 'bundle.js',
    path: path.join(__dirname, '../dist'),
    // 在引入静态资源时,从根路径开始引入,即/bundle.js
    publicPath: '/'
  },

效果: