/**
- @description 自定义router
- @author chendada */
let _Vue = null export default class VueRouter { static install (Vue) { // 1. 判断是否注册过,如果已注册过,不再注册 // 2. 缓存当前的Vue构造函数 // 3. 挂载$router到vue实例下,利用混入,混入到每个组件的实例中
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
_Vue = Vue
// Vue.prototype.$router = this.$options.router
// 为什么不能直接在Vue的原型对象上挂载$router呢,因为在当前函数中,this指向的是并不是vue实例,故this.$options是不存在的
// 故我们通过混入的形式,在vue实例的beforeCreate生命周期中去给vue原型添加$router
Vue.mixin({
beforeCreate () {
if (_Vue.prototype.$router) {
return
}
_Vue.prototype.$router = this.$options.router
}
})
}
constructor (options) { // 实现routerMap(用于存储路由与组件的映射关系) this.routerMap = {} this.createRouteMap(options.routes || [])
// 记录当前路由模式,如果不传模式,默认给history模式
this.mode = options.mode || 'history'
// 定义一个响应式对象
this.data = _Vue.observable({
current: this.mode === 'history' ? '/' : '#/' // 存放当前url地址
})
// 初始化公用组件
this.initComponents()
// 注册相关事件
this.initEvent()
}
initComponents () { // 初始化router-link组件 this.initLink()
// 初始化router-view组件
this.initView()
}
// 注册相关事件 initEvent () { if (this.mode === 'history') { /** * @description popstate事件,当浏览器历史发生变化时,会触发该事件,主要用于处理当浏览器点击前进后退时, * 触发该事件,去改变current变量的值,从而触发对应组件的重新渲染,从而改变页面 * @ps history模式下,浏览器历史发生变化才会触发popstate事件 */
// 用户首次打开时,加载当前路由对应的组件
window.addEventListener('load', () => {
const path = location.pathname ? location.pathname : '/'
this.data.current = path
})
window.addEventListener('popstate', () => {
this.data.current = location.pathname
})
} else {
/**
* @description hashchange事件,当浏览器url的hash地址发生变化时,会触发该事件,主要用于处理当浏览器url的hash地址发生变化时
* 触发该事件,去改变current变量的值,从而触发对应组件的重新渲染,从而改变页面
* @ps hash模式下,浏览器url的hash发生变化时才会触发hashchange事件
*/
// 页面首次加载时,加载当前路由对应的组件
window.addEventListener('load', () => {
// 页面加载时,如果没有hash符,添加hash符
location.hash = location.hash || '/'
this.data.current = location.hash
})
window.addEventListener('hashchange', () => {
this.data.current = location.hash
})
}
}
// 注册router-link组件 initLink () { _Vue.component('router-link', { props: { to: String }, render (h) { return h('a', { attrs: { href: this.to }, on: { click: this.locationHref } }, [this.slots.default]) }, methods: { locationHref (e) { if (this.router.mode === 'history') { /** * @description pushState用于改变浏览器跳转地址 * 参数有3个 第一个参数:一个对象,后续触发popState事件时,传给popState的事件的事件对象 * 第二个参数:是title,网页标题 * 第三个参数:需要跳转的url地址 */ history.pushState({}, '', this.to)
// 更新data下的current变量的值(该变量用于记录当前url地址,当url发生变化时,需要改变这个变量的值)
// 因current是响应式数据,故当值发生变化时,会触发对应组件的重新渲染,从而当url发生变化时,页面也会发生变化
this.$router.data.current = this.to
} else {
window.location.hash = `#${this.to}`
this.$router.data.current = `#${this.to}`
}
// 阻止a标签默认事件,这里需要阻止a标签的href跳转,因为a标签的href跳转是会让浏览器直接向服务器去发送请求的
e.preventDefault()
}
}
})
}
// 注册router-view组件 initView () { const self = this _Vue.component('router-view', { render (h) { // 从路由表中获取当前path对应的component组件 let component = null if (this.$router.mode === 'history') { component = self.routerMap[self.data.current] } else { // hash模式下时,截取#后面的地址作为path路径,然后再去路由表中匹配对应的组件 const path = self.data.current.slice(1, self.data.current.length) component = self.routerMap[path] } // 渲染对应的组件 return h(component) } }) }
// 解析路由表,得到路由与组件的映射关系
createRouteMap (routes, parentPath) {
if (routes && routes.length && routes.length > 0) {
routes.forEach((item) => {
let cPath = ''
if (parentPath) {
cPath = ${parentPath}/${item.path}
} else {
cPath = item.path
}
this.routerMap[cPath] = item.component
if (item.children && item.children.length > 0) {
this.createRouteMap(item.children, cPath)
}
})
}
}
}