自定义插件vue-router简单实现hashRouter设计思路
步骤
1.挂载 vue.prototype.$router
2.声明两个组件
router-view this.$router.current=>component => h(component)
router-link h('a',{attrs:{href:'#'+this.to}},this.$slots.default)
3.url的监听:window hashchange的改变
4.定义响应式current,defineReactive()
实现VueRouter类
let Vue
// vueRouter是一个类,一个插件
class VueRouter {
constructor(options) {
this.$options = options
}
}
VueRouter.install = function (_Vue) {
//保存引用
Vue = _Vue
//挂在一下vueRouter到vue原型
//利用全局混入,全局执行代码,在vue实例beforeCreate时获取到router,因为在main.js中生成vue实例在VueRouter挂载到Vue之后,所以常规无法获取到router,
Vue.mixin({
beforeCreate() {
if (this.$options.router) { //避免每次实例创建都触发,只有根实例上存在的才触发。
Vue.prototype.$router = this.$options.router
}
}
})
//声明两个全局组件,router-viewport,router-link
Vue.component('router-link', {
props: {
to: {
type: String,
required: true
}
},
//预编译情况下,template是不能使用的,这里要使用render
// template:'<a>123</a>'
render(h) {
// return <a href={'#'+this.to}>{this.$slots.default}</a> jsx语法也可以使用
return h('a', {
attrs: { href: "#" + this.to }
}, this.$slots.default) //this.$slots.default 获取内容
}
})
Vue.component('router-view', {
render(h) {
return h('div', 'router-view')
}
})
}
export default VueRouter
这里有个难点,就是如何将router挂载到vue原型上,我们采用了mixin的用法,在vue实例beforeCreate时获取到router,并挂在到实例上。
怎么理解呢?在router.js中,Vue.use(VueRouter)触发install方法,但是在此时,并没有生成router,是在new VueRouter之后生成的router,并且挂载到了Vue,此时才有VueRouter,利用全局混入,延迟执行挂载到Vue原型上,这样就可以获取到router实例了。
import Vue from 'vue'
// import VueRouter from 'vue-router'
import VueRouter from '@/krouter/kvue-router'
Vue.use(VueRouter)//执行install方法,router还未被创建
const routes = [
{
path: '/home',
name: 'home',
component: () => import('@/components/Home.vue')
},
{
path: '/about',
name: 'about',
component: () => import('@/components/About.vue')
},
]
const router = new VueRouter({
routes
})
export default router
import Vue from 'vue'
import App from './App.vue'
import router from './router/router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')
监听url变化,渲染组件
class VueRouter {
constructor(options) {
this.$options = options
//声明一个响应式current
//渲染函数如果压重复执行,必须依赖响应式数据
Vue.util.defineReactive(this,'current',window.location.hash.slice(1) || '/') //需要#后面的部分
//监听url变化
window.addEventListener('hashchange', () => {
this.current = window.location.hash.slice(1)
})
}
}
这里使用Vue工具类定义响应式current,必须是响应式current,否则不生效,在router-view处读取并渲染对应组件,在这里Vue的原型对象已经挂载了this.$router,this.$router又是VueRouter的实例,所以在这上面能够找到对应的current属性,所以在current变化的时候,在引用到current的地方都会被通知到,然后渲染组件。
Vue.component('router-view', {
render(h) {
const obj = this.$router.$options.routes.find(el=>this.$router.current==el.path)
return h(obj.component)
}
})
但是每次都要去遍历循环字典,也不是很合理,我们可以优化一下,缓存一下path和route映射关系
路由嵌套
思路:参考源码思路,给当前routerView深度标记,然后根据当前页面路由获取当前路由数组,其中包括一级和二级路由,然后使用depth获取对应的组件,然后并渲染。
Vue.component('router-view', {
render(h) {
//标记当前router-view深度
this.$vnode.data.routerView = true
let depth = 0
let parent = this.$parent
while (parent) {
const vnodeData = parent.$vnode && parent.$vnode.data
if (vnodeData) {
if (vnodeData.routerView) {
//说明当前的parent是一个routerView
depth++
}
}
parent = parent.$parent
}
let component = null
const route = this.$router.matched[depth]
if (route) {
component = route.component
}
return h(component)
}
})
此时我们不再使用current来做响应式,使用matched数组获取匹配关系,VueRouter实例创建时调用match方法,获取路由数组,并且在路由发生变化时重新获取路由数组matched。
class VueRouter {
constructor(options) {
this.$options = options
//声明一个响应式current
//渲染函数如果压重复执行,必须依赖响应式数据
// Vue.util.defineReactive(this,'current',window.location.hash.slice(1) || '/') //需要#后面的部分
this.current = window.location.hash.slice(1) || '/' //初始值
Vue.util.defineReactive(this, 'matched', [])
// match方法可以递归的遍历路由表获取匹配关系的数组
this.match()
//监听url变化
window.addEventListener('hashchange', () => {
this.current = window.location.hash.slice(1)
this.matched=[]
this.match()
})
}
match(routes) {
routes = routes || this.$options.routes
console.log(routes);
//递归遍历路由表
for (const route of routes) {
if (this.current.indexOf(route.path)!=-1) {
this.matched.push(route)
if (route.children) {
this.match(route.children)
}
return
}
}
}
}
附完整代码:
//vue-router插件
let Vue
// vueRouter是一个类,一个插件
class VueRouter {
constructor(options) {
this.$options = options
//声明一个响应式current
//渲染函数如果压重复执行,必须依赖响应式数据
// Vue.util.defineReactive(this,'current',window.location.hash.slice(1) || '/') //需要#后面的部分
this.current = window.location.hash.slice(1) || '/' //初始值
Vue.util.defineReactive(this, 'matched', [])
// match方法可以递归的遍历路由表获取匹配关系的数组
this.match()
//监听url变化
window.addEventListener('hashchange', () => {
this.current = window.location.hash.slice(1)
this.matched=[]
this.match()
})
}
match(routes) {
routes = routes || this.$options.routes
//递归遍历路由表
for (const route of routes) {
if (this.current.indexOf(route.path)!=-1) {
this.matched.push(route)
if (route.children) {
this.match(route.children)
}
return
}
}
console.log(this.matched);
}
}
VueRouter.install = function (_Vue) {
//保存引用
Vue = _Vue
//挂在一下vueRouter到vue原型
//利用全局混入,全局执行代码,在vue实例beforeCreate时获取到router,因为在main.js中生成vue实例在VueRouter挂载到Vue之后,所以常规无法获取到router,
Vue.mixin({
beforeCreate() {
if (this.$options.router) { //避免每次实例创建都触发,只有根实例上存在的才触发。
Vue.prototype.$router = this.$options.router
}
}
})
//声明两个全局组件,router-viewport,router-link
Vue.component('router-link', {
props: {
to: {
type: String,
required: true
}
},
//预编译情况下,template是不能使用的,这里要使用render
// template:'<a>123</a>'
render(h) {
// return <a href={'#'+this.to}>{this.$slots.default}</a> jsx语法也可以使用
return h('a', {
attrs: { href: "#" + this.to }
}, this.$slots.default) //this.$slots.default 获取内容
}
})
Vue.component('router-view', {
render(h) {
//标记当前router-view深度
this.$vnode.data.routerView = true
let depth = 0
let parent = this.$parent
while (parent) {
const vnodeData = parent.$vnode && parent.$vnode.data
if (vnodeData) {
if (vnodeData.routerView) {
//说明当前的parent是一个routerView
depth++
}
}
parent = parent.$parent
}
let component = null
console.log(this.$router.matched);
const route = this.$router.matched[depth]
if (route) {
component = route.component
}
return h(component)
}
})
}
export default VueRouter
import Vue from 'vue'
// import VueRouter from 'vue-router'
import VueRouter from '@/krouter/kvue-router'
Vue.use(VueRouter)//执行install方法,router还未被创建
const routes = [
{
path: '/home',
name: 'home',
component: () => import('@/components/Home.vue')
},
{
path: '/about',
name: 'about',
component: () => import('@/components/About.vue'),
children: [
{
path: '/about/info',
component: {
render(h) {
return h('div', 'info page')
}
}
}
]
},
]
const router = new VueRouter({
routes
})
export default router