来到框架底层简单实现的第二篇,这一篇就来研究一下怎么实现一个SPA的Router。
原理
目前主要的实现router原理有两种,一种是使用history,一种是使用hash。
History 路由
顾名思义,就是使用了window
下的history
对象实现。得益于history
提供了pushState()
和replaceState()
方法,可以对历史栈进行操作,从而达到路由的目的。
由于本文以Hash方式来实现路由,因此不多做解说。
Hash
所谓的Hash其实就是我们常说的锚点,用来做页面的跳转,在这里我们就通过修改Hash,并且通过监听hashchange
来对页面进行切换。
PS: Vue中,页面切换会涉及到VDOMremounted
(非keep-alive)。这里为了焦点放在路由上,不会加入VDOM内容。
内容切换
首先看看HTML:1
2
3
4
5
6
7<div id="router-list">
<a href="/">主页</a>
<a href="/blog">博客</a>
<a href="/resume">简历</a>
</div>
<div id="view">
</div>
非常简单,定义了一个路由列表和一个展示区域。
接下来,用object的形式来表示这几个路由(当然路由view完全可以动态生成):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const router = [{
id: 0,
path: '/',
title: '主页',
template: '<h1>Hello</h1>'
},
{
id: 1,
path: '/blog',
title: '博客',
template: '<h1>Check it out</h1>'
},
{
id: 2,
path: '/resume',
title: '简历',
template: '<h1>My Resume</h1>'
}
]
这里的template就是我们的页面内容,然后开始定义我们的路由类:1
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
41
42
43
44
45
46
47
48
49
50
51
52function SueRouter(config) {
this.config = config
this.router = config.router
this.viewZone = document.querySelector(config.el)
if (!this.router) throw new Error('`config.router` can not be null.')
// init path
const initPath = window.location.hash.length > 0 ? window.location.hash.substring(1) : '/'
this.currentPage = this.router.find(r => r.path === initPath) || this.router[0]
this.bindListener()
// first render
this.render()
}
SueRouter.prototype.bindListener = function () {
// bind list
const list = document.querySelector('#router-list')
list.addEventListener('click', (evt) => {
if (evt.target.nodeName === 'A') {
// 重要,需要拦截超链接的默认行为
evt.preventDefault()
window.location.hash = evt.target.getAttribute('href')
return false
}
})
window.addEventListener("hashchange", () => {
this.routerChange()
})
}
SueRouter.prototype.render = function () {
if (this.currentPage) {
this.viewZone.innerHTML = this.currentPage.template
}
}
SueRouter.prototype.routerLinkTo = function (path) {
const page = this.router.find(r => r.path === path)
this.currentPage = page
this.render()
}
SueRouter.prototype.routerChange = function () {
this.routerLinkTo(window.location.hash.substring(1))
}
new SueRouter({
router,
el: '#view'
})
可以看到,其实核心非常简单:
- 捕捉超链接,将
window.location.hash
设为超链接上的链接地址 - 监听
hashchange
事件,当window.location.hash
改变的时候就会触发 - 处理监听事件,获取当前的
hash
然后找到对应的route配置,然后渲染新的页面
页面缓存
对于SPA页面的缓存存在的意义就是对于一些页面,在recreate
和remount
阶段的时候会带来较大消耗,所以需要缓存。
以Vue的keep-alive
为例,实际上缓存的是vnode
的组件实例,因为,页面切换的时候,始终逃不过reflow
和repaint
,这部分是难以优化的,而对于VDOM来说,可以缓存VDOM,那就可以省掉了再次create/mount
的消耗。
因此在我们这个例子中,实际上我们的页面渲染是直接替换的,因此没有VDOM方面的消耗考虑。
总结
总的来说,实现一个简单的router是很容易的。