# 深度剖析 前端路由原理
# 深度剖析 前端路由原理
# 前言
前端三大框架 Angular、React、Vue
他们都有自己的路由方案,都是基于前端路由原理进行封装实现的,我们在实际项目开发中会遇到一些坑,弄明白其中的实现原理才能在开发中游刃有余。
# 什么是路由
路由的概念起源于服务端,在以前前后端没有分离的时候,由后端来控制路由,当接收到客户端发来的HTTP
请求,就会根据所请求的URL
,来查找对应的映射函数,然后执行该函数,并将函数的返回值发送给客户端。
对于最简单的静态资源服务器,可以认为,所有的URL
的映射函数就是一个文件读取操作,对于动态资源,映射函数可能是一个数据库读取操作,也可能是进行一些数据的处理等等,然后根据这些数据渲染模板,再将渲染好的页面返回。
这样处理的缺点和有优点都很明显:
- 好处:安全性好、SEO好;
- 缺点:加大了服务器的压力,不利于用户体验,代码比较冗余不好维护
因为服务端路由有自己的局限性,前端路由才能找到了自己的发挥空间,对于前端路由来说,路由的映射函数,通常是一些DOM的显示和隐藏操作,这样,当访问不同的路径的时候,会显示不同的页面组件,在现在的单页面应用中,前端路由描述的URL和UI之间的单向映射关系,即URL变化引起的UI页面的更新。
# 前端路由的核心问题
- 如何检测到URL的变化?
- 如何改变URL却不引起页面的刷新?
前端路由主要有以下两种实现方案:
- 1、Hash
- 2、History
在
Hash
方式中,我们可以通过hashchange
事件监听URL的变化,以下的场景会触发hashchange
事件:通过浏览器前进后退改变URL、通过标签改变URL、通过window.location
改变URL Hash 是 URL中 # 以及后面的部分,改变URL中的hash部分不会引起页面的刷新。
在
History
方式中,我们可以通过popstate
事件监听 URL的变化, 我们可以调用pushState
和replaceState
两个方法,改变URL而不引起页面的刷新,值得注意的是,通过浏览器前进后退改变时候回触发popState
事件,而通过pushState、replaceState
或标签改变 URL 并不会触发popstate
事件,因此我们需要手动拦截监听。
当然,前端路由也存在缺陷:使用浏览器的前进、后退的时候重新发送了请求,重新获取数据,没有合理的利用缓存,但是总的来说,现在前端路由已经是实现路由的主要方式了,前端三大框架 Angular、React、Vue
,它们的路由解决方案 angular/router、react-router、vue-router
都是基于前端路由进行开发的,因此将前端路由进行了解和掌握是很有必要的,下面我们分别对两种常见的前端路由模式 Hash 和 History 进行讲解。
# 前端路由的两种实现
# Hash 模式
# 实现原理
早期的前端路由实现就是基于location.hash
来实现的,其实现原理也很简单。location.hash
的值就是url中 # 后面的内容,比如这个网址,它的location.hash
的值为 '#/search'
https://www.word.com#search
此外,hash也存在下面几个特性
- URL中hash值只是客户端的一种状态,也就是说当向服务器发出请求的时候,hash部分不会被发送。
- hash值的改变,都会在浏览器的访问历史中添加一个记录,因此我们能够通过浏览器的回退、前进控制hash的切换
- 我们可以使用hashChange来监听hash的变化
# 两种方式触发hash
a标签:
通过a标签设置 href属性,当用户点击这个标签后,URL就会发生改变,也就会触发 hashchange
事件了;
<a href="#search">search</a>
js控制实现:
另外一种就是js控制实现,从而改变URL
location.hash="#search"
# js实现
我们先定义一个父类 BaseRouter,用于实现 Hash路由和 History 一些公共方法
export class BaseRouter {
// list 表示路由表
constructor(list) {
this.list = list
}
// 页面渲染函数
render() {
let ele = this.list.find(ele => ele.path === state);
ele ? ele: this.list.find( ele => ele.path === "*");
ELEMENT.innerText = ele.component;
}
}
2
3
4
5
6
7
8
9
10
11
12
我们简单实现了 push 压入功能,go 前进后退功能
export class HashRouter extends BaseRouter {
constructor(list) {
super(list);
this.handler();
// 监听 hashchange 事件
window.addEventListener('hashchange', e => {
this.handler()
})
}
// hash改变的时候,重新渲染页面
handler() {
this.render(this.getState());
}
// 获取hash值
getState() {
const hash = window.location.hash;
return hash ? hash.slice(1): '/'
}
// push 新的页面
push(path) {
window.location.hash = path;
}
// 获取默认页面的url
getUrl(path) {
const href = window.location.href;
const i = href.indexOf('#');
const base = i >= 0 href.slice(0,i) : href;
return base + '#' + path;
}
// 替换页面
replace(path) {
window.location.replace(this.getUrl(path));
}
// 前进 or 后退浏览历史
go(n){
window.history.go(n)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# History 模式
# 实现原理
前面的hash 虽然也很不错,但是使用上都需要加上 # ,并不是很美观,因此到了HTML5 又提供了 History API 来实现URL变化。其中做最主要的api有两个:
history.pushState() 和 history.replaceState() 这两个api可以在不进行刷新的状态下操作浏览器的历史记录,唯一不同的是,前者是新增加一个历史记录,后者是直接替换掉当前的历史记录。
window.history.pushState(null,null,path);
window.history.repalceState(null,null,path);
2
此外,history 还存在几个特性:
- pushState 和 replaceState 的标题(title)一般浏览器会忽略,最好传入null
- 我们可以使用 popstate 事件来监听 url 变化
- history.pushState() 或者 history.repalceState() 不会触发 popstate 事件,这个时候我们应该手动触发页面渲染。
# js实现
同样简单实现了push压入功能、 go 前进/后退功能
export class HistoryRouter extends BaseRouter {
constructor() {
super(list) {
this.list = list;
}
this.handler();
// 监听 popState 事件
window.addEventListener('popstate', e => {
console.log('触发 popstate。。。');
this.handler();
})
// 渲染页面
handler() {
this.render(this.getState());
}
// 获取url
getState() {
const path = window.location.pathname;
return path ? path : '/';
}
// push 页面
push(path) {
history.replaceState(null,null,path);
this.handler();
}
// 前进 or 后退
go(n) {
window.history.go(n)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 总结
本文介绍了什么是路由,前端路由的起源,以及分析了两种前端路由: Hash模式和History模式原理以及简单的实现。 通过对于本文对前端路由的原理的掌握,这个时候就可以基于原理去阅读react-router和 vue-router的源码实现了。