需求分析
话不多说,直接上代码:
1 | <div id="demo"> |
插件基本结构
根据我们 vue-router 的使用方式,我们写出插件的基本结构如下:1
2
3
4
5class VueRouter {
constructor(options) {}
}
VueRouter.install = function (Vue) {}
初次渲染路由
我们先不考虑路由变化的情况,仅实现第一次渲染。首先我们实现下我们的 VueRouter:1
2
3
4
5
6
7
8
9
10
11
12
13class VueRouter {
constructor(options) {
this.routes = options.routes
this.routeMap = {}
this.routes.forEach((route) => {
this.routeMap[route.path] = route
})
// 当前路由
this.current = window.location.hash.slice(1)
}
}
我们通过一个 routeMap 来存储路由和组件的对应关系以方便后续进行索引,同时用一个 current 变量来记录当前地址栏中的路由。
然后,我们需要在插件安装的时候定义下我们的 router-view 组件:
1 | VueRouter.install = function (Vue) { |
我们需要拿到路由表以及当前的路由,但是从哪去获取呢?很明显,这个信息存在于 VueRouter 实例之上,但是我们执行 VueRouter.install 的时候还没有该实例,是不是就没有办法了呢?注意到 VueRouter 实例是传递给了 Vue 的根实例的,所以我们可以在根组件的生命周期中将 router 这个对象共享给所有组件,这样 router-view 在渲染的时候就可以拿到所需要的信息了:
1 | VueRouter.install = function (Vue) { |
监听路由变化
我们使用 hashchange 来监听路由的变化,当路由变化的时候将当前 hash 赋值给 this.current。当然,为了使得当 this.current 发生变化的时候能够触发视图重新渲染,我们需要将 current 属性定义为响应式的:1
2
3
4
5
6
7
8
9
10
11
12
13class VueRouter {
constructor(options) {
...
Vue.util.defineReactive(this, 'current', window.location.hash.slice(1))
window.addEventListener('hashchange', this.onHashChange.bind(this))
}
onHashChange() {
this.current = window.location.hash.slice(1)
}
}
实现 router-link
该组件接受一个名为 to 的属性,并渲染出一个 a 标签,例如:<a href='#/about'>About</a>。1
2
3
4
5
6
7
8
9
10
11Vue.component('router-link', {
props: {
to: {
type: String,
default: '',
},
},
render(h) {
return h('a', {attrs: {href: '#' + this.to}}, this.$slots.default)
},
})
总结
本文通过层层递进,最终实现了一个简单的 vue-router。把复杂的东西拆解后,一切感觉都是水到渠成啊。