和官方的使用方式一样,通过Vue.use来注册插件。
import Vue from 'vue'
import App from './App.vue'
//模拟代码路径
import VueRouter from './vuerouter'
import Home from './components/Home'
import About from './components/About'
Vue.config.productionTip = false
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]
const router = new VueRouter({
routes
})
new Vue({
router,
render: h => h(App),
}).$mount('#app')
模拟思路
这里我们只实现hash模式的,首先我们使用ES6的class来实现:
export default class VueRouter {...}
既然是通过Vue.use来使用,所以需要一个静态install方法:
let _Vue = null
export default class VueRouter {
static install(Vue) {
// 判断是否已经安装
if (VueRouter.install.installed) return
// 方法也是对象,给个installed来记录插件已经被安装
VueRouter.install.installed = true
// 把Vue构造函数记录到全局变量,后续会用到
_Vue = Vue
// 把创建Vue实例时候传入的router记录到Vue实例上,这样就可以在每个组件中使用this.$router来访问router对象
// 为什么要使用mixin?
// 因为我们要获取到Vue实例上$options.router对象,就是main.js当中的
// new Vue({
// router, // 这个对象!!!!!!
// render: h => h(App),
// }).$mount('#app')
// 正常情况下是获取不到这个Vue实例的,所以就用到了mixin全局混入,在beforeCreate钩子中获取router对象
Vue.mixin({
beforeCreate() {
// 因为只有在main.js文件中的Vue实例有$options.router,所以这里必须做下判断
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
// 这里进行初始化,代码稍后说
this.$options.router.init()
}
}
})
}
...
}
接下来是VueRouter constructor中代码(初始化):
let _Vue = null
export default class VueRouter {
...
constructor(options) {
// 记录传入的options
this.options = options
// 这里是要将options.routes数组中的路由信息转换成一个由path -> component组成的映射表
this.routeMap = {}
//current是当前路由地址,当current改变后页面就要进行跳转,所以很显然必须要做响应式处理
this.data = _Vue.observable({
// 初始值这样设置是为了保证在刷新页面后加载正确的component
current: window.location.hash.substring(1) || '/'
})
}
}
接下来是创建routeMap的代码:
let _Vue = null
export default class VueRouter {
...
createRouteMap() {
// 这里只简单做了一层循环,没有考虑子路由
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
}
接下来是创建router-link和router-view组件:
initComponents(Vue) {
Vue.component('router-link', {
// 需要一个to属性,接收要跳转的路由path
props: {
to: String
},
// 这里使用template,所以要使用包含编译器的版本的Vue,当然也可以使用render函数
// 使用slot来接收router-link标签之间的内容
template: '<a :href="`#${to}`"><slot></slot></a>'
})
const _this = this
Vue.component('router-view', {
// 这里需要使用render函数
render(h) {
// 获取当前的路由component
const component = _this.routeMap[_this.data.current]
// h函数创建vNode
return h(component)
}
})
}
接下来是创建事件监听,每当hash变化就改变current值:
initEvent() {
window.addEventListener('hashchange', (e) => {
this.data.current = e.currentTarget.location.hash.substring(1)
})
}
最后创建一个init方法将上面的方法包裹起来:
init() {
this.createRouteMap()
this.initComponents(_Vue)
this.initEvent()
}
这样一个最简单的vue-router就实现啦!
以下是完整代码:
let _Vue = null
export default class VueRouter {
static install(Vue) {
// 判断是否已经安装
if (VueRouter.install.installed) return
VueRouter.install.installed = true
// 把Vue构造函数记录到全局变量
_Vue = Vue
// 把创建Vue实例时候传入的router记录到Vue实例上
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
this.data = _Vue.observable({
current: window.location.hash.substring(1) || '/'
})
}
createRouteMap() {
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
initComponents(Vue) {
Vue.component('router-link', {
props: {
to: String
},
template: '<a :href="`#${to}`"><slot></slot></a>'
})
const _this = this
Vue.component('router-view', {
render(h) {
const component = _this.routeMap[_this.data.current]
return h(component)
}
})
}
initEvent() {
window.addEventListener('hashchange', (e) => {
this.data.current = e.currentTarget.location.hash.substring(1)
})
}
init() {
this.createRouteMap()
this.initComponents(_Vue)
this.initEvent()
}
}
demo演示: