前言
在一个网页里面,多个子网页之间需要相互切换
类似于这样:
之前的解决方式是前后端配合,一个子网页对应服务器上的一个子文件夹,用户每点击一个按钮就向后端请求数据:
通过 v-show 去控制组件的显示与隐藏
代码逻辑如下:
<div v-for="(item,index) in list" v-show = {{activeIndex === index}}>{{item.title}}</div>
缺点
可以看到:由于 SPA 中用户的交互是 通过 JS 改变 HTML 内容 来实现的,也就是说 页面本身的 url 并没有变化,这导致了三个问题:
- SPA
无法记住用户的操作记录,无论是刷新、前进还是后退,都无法展示用户真实的期望内容。 - SPA
首次加载时间长,需要把所有的内容加载出来(包括看不见的) - SPA 中虽然由于业务的不同会有多种页面展示形式,但只有一个 url,
对 SEO 不友好,不方便搜索引擎进行收录。
这里不用 v-if 而是用 v-show 的原因是:Tab 页面会不断切换,如果使用 v-if 就会造成 DOM 节点的摘除和接上,可能会引起回流
为了解决上述三个问题,就有了 前端路由 的概念
定义
什么是路由?
路由这个概念最先是后端出现的,简单来说 路由就是用来跟后端服务器进行交互的一种方式,通过
不同的路径,来请求不同的资源,请求不同的页面是路由的其中一种功能
什么是前端路由?
简单的说,就是在保证只有一个 HTML 页面,且与用户交互时不刷新和跳转页面的同时,为 SPA 中的每个视图展示形式匹配一个特殊的 url。在刷新、前进、后退和SEO时均通过这个特殊的 url 来实现。
为实现这一目标,我们需要做到以下两点:
- 改变 url 且
不让浏览器像服务器发送请求。 - 可以
监听、记录到 url 的变化
vue-router 中就有两种路由模式:hash 模式、history 模式
hash 模式
原理
- 早期的前端路由的实现就是基于 location.hash 来实现的,location.hash 的值就是 URL 中 # 及后面的内容。它的实现原理就是监听 # 后面的内容来发起 Ajax 请求来进行局部更新,而不需要刷新整个页面。
- 使用 hashchange 事件来监听 URL 的变化,以下这几种情况改变 URL 都会触发 hashchange 事件:
- 浏览器前进后退改变 URL
- a 标签改变 URL
- window.location 改变 URL
使用
<ul id="menu">
<li>
<a href="#index">首页</a>
</li>
<li>
<a href="#news">资讯</a>
</li>
<li>
<a href="#user">个人中心</a>
</li>
</ul>
<div id="app"></div>
function hashChange(e){
let app = document.getElementById('app')
switch (location.hash) {
case '#index':
app.innerHTML = '<h1>这是首页内容</h1>'
break
case '#news':
app.innerHTML = '<h1>这是新闻内容</h1>'
break
case '#user':
app.innerHTML = '<h1>这是个人中心内容</h1>'
break
default:
app.innerHTML = '<h1>404</h1>'
}
}
window.onhashchange = hashChange
hashChange() // 初始状态渲染
效果
优点
- 兼容
低版本浏览器 - 只有 # 符号之前的内容才会包含在请求中被发送到后端,也就是说就算
后端没有对路由全覆盖,但是不会返回 404 错误 - hash 值的改变,都会在浏览器的访问历史中
增加一个记录,所以可以通过浏览器的回退、前进按钮控制 hash 的切换会覆盖锚点定位元素的功能
缺点
不太美观,# 后面传输的数据复杂的话会出现问题
history 模式
原理
- history 提供了 pushState 和 replaceState 两个方法来记录路由状态,这两个方法改变 URL 不会引起页面刷新
- history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同:通过浏览器前进后退改变 URL 时会触发 popstate 事件,通过 pushState/replaceState 或 a 标签改变 URL 不会触发 popstate 事件。好在我们可以拦截 pushState/replaceState 的调用和 a 标签的点击事件来检测 URL 变化,所以监听 URL 变化可以实现,只是没有 hashchange 那么方便。
- pushState(state, title, url) 和 replaceState(state, title, url)都可以接受三个相同的参数
使用
<ul id="menu">
<li>
<a href="/index">首页</a>
</li>
<li>
<a href="/news">资讯</a>
</li>
<li>
<a href="/user">个人中心</a>
</li>
</ul>
<div id="app"></div>
document.querySelector('#menu').addEventListener('click',function (e) {
if(e.target.nodeName ==='A'){
e.preventDefault()
let path = e.target.getAttribute('href') //获取超链接的href,改为pushState跳转,不刷新页面
window.history.pushState({},'',path) //修改浏览器中显示的url地址
render(path) //根据path,更改页面内容
}
})
function render(path) {
let app = document.getElementById('app')
switch (path) {
case '/index':
app.innerHTML = '<h1>这是首页内容</h1>'
break
case '/news':
app.innerHTML = '<h1>这是新闻内容</h1>'
break
case '/user':
app.innerHTML = '<h1>这是个人中心内容</h1>'
break
default:
app.innerHTML = '<h1>404</h1>'
}
}
window.onpopstate = function (e) {
render(location.pathname)
}
render('/index')
优点
- 使用简单,比较美观
- pushState() 设置新的 URL 可以是任意与当前 URL 同源的 URL,而hash只能改变#后面的内容,因此只能设置与当前URL同文档的URL
- pushState() 设置的 URL 与当前 URL 一模一样时也会被添加到历史记录栈中,而 hash #后面的内容必须被修改才会被添加到新的记录栈中
- pushState() 可以通过 stateObject 参数添加任意类型的数据到记录中,而hash只能添加短字符串
- pushState() 可额外设置 title 属性供后续使用
缺点
- 前端的 URL 必须
和向发送请求后端 URL 保持一致,否则会报 404 错误 - 由于 History API 的缘故,低版本浏览器有
兼容问题
两种模式的不同使用场景
- 从上文可见,hash 模式下 url 会带有#,当你希望 url 更优雅时,可以使用 history 模式。
- 当使用 history 模式时,需要注意在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
- 当需要
兼容低版本的浏览器时,建议使用 hash 模式。 - 当需要添加
任意类型数据到记录时,可以使用 history 模式