一、什么是前端路由
- 路由这个概念最先是后端出现的。在以前用模板引擎开发页面时,经常会看到这样
http://www.xxx.com/login
http://www.xxx.com/register
http://www.xxx.com/home
复制代码
- 大致流程可以看成这样:
-
浏览器发出请求
-
服务器监听到80端口(或443)有请求过来,并解析url路径
-
根据服务器的路由配置,返回相应信息(可以是 html 字串,也可以是 json 数据,图片等)
- 浏览器根据数据包的 Content-Type 来决定如何解析数据
- 后端路由简单来说路由就是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不同的资源,请求不同的页面是路由的其中一种功能。而前端路由则是在 Web 前端单页应用 SPA(Single Page Application)中,路由描述的是 URL 与 UI 之间的映射关系,这种映射是单向的,即 URL 变化引起 UI 更新(无需刷新页面)。
二、前端路由模式
- 前端路由有两种模式:分别为 hash模式 和 history模式。
1. hash 模式
随着 ajax 的流行,异步数据请求交互运行在不刷新浏览器的情况下进行。而异步交互体验的更高级版本就是 SPA —— 单页应用。单页应用不仅仅是在页面交互是无刷新的,连页面跳转都是无刷新的,为了实现单页应用,所以就有了前端路由。
类似于服务端路由,前端路由实现起来其实也很简单,就是匹配不同的 url 路径,进行解析,然后动态的渲染出区域 html 内容。但是这样存在一个问题,就是 url 每次变化的时候,都会造成页面的刷新。那解决问题的思路便是在改变 url 的情况下,保证页面的不刷新。在 2014 年之前,大家是通过 hash 来实现路由,url hash 就是类似于:
http://www.xxx.com/#/login
http://www.xxx.com/#/home
复制代码
这种 #。后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。另外每次 hash 值的变化,还会触发 hashchange 这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。然后我们便可以监听hashchange来实现更新页面部分内容的操作:
2. history 模式
14年后,因为HTML5标准发布。多了两个 API,pushState 和 replaceState,通过这两个 API 可以改变 url 地址且不会发送请求。同时还有popstate 事件。通过这些就能用另一种方式来实现前端路由了,但原理都是跟 hash 实现相同的。用了 HTML5 的实现,单页路由的 url 就不会多出一个#,变得更加美观。但因为没有 # 号,所以当用户刷新页面之类的操作时,浏览器还是会给服务器发送请求。为了避免出现这种情况,所以这个实现需要服务器的支持,需要把所有路由都重定向到根页面。
三、如何实现前端路由?
-
要实现前端路由,需要解决两个核心:
-
如何改变 URL 却不引起页面刷新?
-
如何检测 URL 变化了?
-
下面分别使用 hash 和 history 两种实现方式回答上面的两个核心问题。
1. hash 实现
hash 是 URL 中 hash (#) 及后面的那部分,常用作锚点在页面内进行导航,改变 URL 中的 hash 部分不会引起页面刷新通过 hashchange 事件监听 URL 的变化。
-
改变 URL 的方式只有这几种:
-
通过浏览器前进后退改变 URL
-
通过 a 标签改变 URL
-
通过window.location改变URL
-
2. history 实现
history 提供了 pushState 和 replaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新。
-
history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同:
-
通过浏览器前进后退改变 URL 时会触发 popstate 事件
-
通过pushState/replaceState或 a 标签改变 URL 不会触发 popstate 事件。
-
好在我们可以拦截 pushState/replaceState的调用和 a 标签的点击事件来检测 URL 变化
-
通过js 调用 history 的 back,go,**forward **方法课触发该事件
-
所以监听 URL 变化可以实现,只是没有 hashchange 那么方便。
四、原生js实现前端路由示例
1. hash实现
<!DOCTYPE html>
<html lang="en">
<body>
<ul>
<ul>
<!-- 定义路由 -->
<li><a href="#/home">home</a></li>
<li><a href="#/about">about</a></li>
<!-- 渲染路由对应的 UI -->
<div id="routeView"></div>
</ul>
</ul>
</body>
<script>
let routerView = routeView
window.addEventListener('hashchange', ()=>{
let hash = location.hash;
routerView.innerHTML = hash
})
window.addEventListener('DOMContentLoaded', ()=>{
if(!location.hash){//如果不存在hash值,那么重定向到#/
location.hash="/"
}else{//如果存在hash值,那就渲染对应UI
let hash = location.hash;
routerView.innerHTML = hash
}
})
</script>
</html>
复制代码
- 解释下上面代码,其实很简单:
-
我们通过a标签的href属性来改变URL的hash值(当然,你触发浏览器的前进后退按钮也可以,或者在控制台输入window.location赋值来改变hash)
-
我们监听hashchange事件。一旦事件触发,就改变routerView的内容,若是在vue中,这改变的应当是router-view这个组件的内容
-
为何又监听了load事件?这时应为页面第一次加载完不会触发 hashchange,因而用load事件来监听hash值,再将视图渲染成对应的内容
2. history 实现
<!DOCTYPE html>
<html lang="en">
<body>
<ul>
<ul>
<li><a href='/home'>home</a></li>
<li><a href='/about'>about</a></li>
<div id="routeView"></div>
</ul>
</ul>
</body>
<script>
let routerView = routeView
window.addEventListener('DOMContentLoaded', onLoad)
window.addEventListener('popstate', ()=>{
routerView.innerHTML = location.pathname
})
function onLoad () {
routerView.innerHTML = location.pathname
var linkList = document.querySelectorAll('a[href]')
linkList.forEach(el => el.addEventListener('click', function (e) {
e.preventDefault()
history.pushState(null, '', el.getAttribute('href'))
routerView.innerHTML = location.pathname
}))
}
</script>
</html>
复制代码
解释下上面代码:
-
我们通过a标签的href属性来改变URL的path值(当然,你触发浏览器的前进后退按钮也可以,或者在控制台输入history.go,back,forward赋值来触发popState事件)。这里需要注意的就是,当改变path值时,默认会触发页面的跳转,所以需要拦截 a 标签点击事件默认行为, 点击时使用 pushState 修改 URL并更新手动 UI,从而实现点击链接更新 URL 和 UI 的效果。
-
我们监听popState事件。一旦事件触发,就改变routerView的内容。load事件则是一样的
-
有个问题:hash模式,也可以用history.go,back,forward来触发hashchange事件吗?
A:也是可以的。因为不管什么模式,浏览器为保存记录都会有一个栈。
哈哈,到这里就差不多,为了搞清楚它的原理也参考了很多文档,东拼西凑的尽量以容易理解的方式把它整理了一下。希望对你们有所帮助,当然,如果本文有什么不妥的地方也欢迎指出。