步驟
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-linkVue.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 = falsenew 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 = truelet depth = 0let parent = this.$parentwhile (parent) {const vnodeData = parent.$vnode && parent.$vnode.dataif (vnodeData) {if (vnodeData.routerView) {//說明當前的parent是一個routerViewdepth++}}parent = parent.$parent}let component = nullconst 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.routesconsole.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-linkVue.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 = truelet depth = 0let parent = this.$parentwhile (parent) {const vnodeData = parent.$vnode && parent.$vnode.dataif (vnodeData) {if (vnodeData.routerView) {//說明當前的parent是一個routerViewdepth++}}parent = parent.$parent}let component = nullconsole.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