跳到主要内容

手写简易版 VueRouter

简易版 VueRouter,直接引入也可以用

let Vue // 插件中的全局 Vue
class VueRouter {
constructor(options) {
this.$options = options
this.current = window.location.hash.slice(1) || '/'

// 利用 vue 工具函数把 matched 设置为响应式数据
Vue.util.defineReactive(this, 'matched', [])
this.match() //递归出 routes 表

window.addEventListener('hashchange', this.onHashchange.bind(this))
window.addEventListener('load', this.onHashchange.bind(this))
}

onHashchange() {
this.current = window.location.hash.slice(1)
this.matched = []
this.match()
}

match(routes) {
routes = routes || this.$options.routes
// 递归遍历
for (const route of routes) {
// / 路由
if (route.path === '/' && this.current === '/') {
this.matched.push(route)
return
}

// /helle/child 子路由
if (route.path !== '/' && this.current.indexOf(route.path) !== -1) {
this.matched.push(route)
if (route.children) {
this.match(route.children) // 递归子路由
}
return
}
}
}
}

// _Vue 在 vue.use 时传入
VueRouter.install = function (_Vue) {
Vue = _Vue //保存参入的 Vue,不用引入也能使用

// 全局混入,挂载 $router,延迟下面逻辑到 router 被添加到 new vue 选项时才执行
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})

// router-link,router-view组件
Vue.component('router-link', {
// template: '<a>aaa</a>'
props: {
to: {
type: String,
require: true
},
},

render(h) {
return h('a', {attrs: {href: '#' + this.to}}, this.$slots.default)
}
})

Vue.component('router-view', {
// template: '<a>bbb</a>'
render(h) {
// 标记当前 router-view 深度
this.$vnode.data.routerView = true

let depth = 0 // 深度
let parent = this.$parent // vue 中 vnode 里有一个 $parent 属性

while (parent) {
const vnodeData = parent.$vnode && parent.$vnode.data
if (vnodeData && vnodeData.routerView) {
// 当前 parent 是一个 router-view
depth++
}
parent = parent.$parent
}


// 通过 depth 获取当前路由对应的组件
let component = null
const route = this.$router.matched[depth]
if (route) {
component = route.component
}
return h(component)
}
})
}

export default VueRouter