【框架底层简单实现(2)】实现一个简单的Router

来到框架底层简单实现的第二篇,这一篇就来研究一下怎么实现一个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
19
const 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
52
function 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'
})

可以看到,其实核心非常简单:

  1. 捕捉超链接,将window.location.hash设为超链接上的链接地址
  2. 监听hashchange事件,当window.location.hash改变的时候就会触发
  3. 处理监听事件,获取当前的hash然后找到对应的route配置,然后渲染新的页面

页面缓存

对于SPA页面的缓存存在的意义就是对于一些页面,在recreateremount阶段的时候会带来较大消耗,所以需要缓存。
以Vue的keep-alive为例,实际上缓存的是vnode的组件实例,因为,页面切换的时候,始终逃不过reflowrepaint,这部分是难以优化的,而对于VDOM来说,可以缓存VDOM,那就可以省掉了再次create/mount的消耗。
因此在我们这个例子中,实际上我们的页面渲染是直接替换的,因此没有VDOM方面的消耗考虑。

总结

总的来说,实现一个简单的router是很容易的。