實際生活中的應用界面,通常由多層嵌套的組件組合而成。同樣地,URL 中各段動態路徑也按某種結構對應嵌套的各層組件,例如:
const router1 = [
{
path: '/',
name: 'home',
component: HelloWorld
},
{
path: '/about',
name: 'about',
component: About,
children: [
{
path: '/about/info',
component: {
render (h) {
return h('div', 'info page')
}
}
}
]
}
]
這裏的/about/info就是一個嵌套路由,要對這樣的路由進行處理,我們在router-view中獲取對應的組件對象時,就有一點不一樣了。之前在KVueRouter類中,我們創建了一個響應式屬性current來保存當前的hash,並創建了一個map來方便在KRouterView.js中獲取對應的組件對象。這對於嵌套路由來說,就不能滿足需求了。
我們重新創建一個響應式屬性match,它是一個數組,同時,我們遞歸遍歷已配置的路由對象,將每一級的路由信息都在這個數組中保存下來,此時,current就不需要是一個響應式數據了。
修改後的KVueRouter類
class KVueRouter {
constructor(options) {
// options就是配置的路由信息,將它作爲KVueRouter對象的一個屬性
this.$options = options
// 創建一個響應式數據,來存儲當前的路由信息,在KRouterView組件中可以直接用這個變量
// defineReactive()方法是vue創建響應式數據的方法,這裏是在KVueRouter對象上面創建一個名爲current的響應式屬性,初始值是'/'
// 從這裏開始不同
// Vue.util.defineReactive(this, 'current', '/') 如果是嵌套路由,當前的current就不能匹配到每個場景了,則不需要響應式
this.current = window.location.hash.slice(1) || '/'
// 嵌套路由的情況下,需要一個數組來保存當前路由的層級,並且需要時響應式的數據,一遍routerview中使用
Vue.util.defineReactive(this, 'match', [])
// 使用遞歸,遍歷當前的路由,並存到this.match中去
this.matchRouter()
// 使用hashchange事件來監聽當前路由的變化,它監聽的是當前連接的錨部分(就是 # 後面的)的變化
// 使用bind方法防止this指向發生變化
window.addEventListener('hashchange', this.onHashChange.bind(this))
window.addEventListener('load', this.onHashChange.bind(this))
// 生成一個map,方便view組件獲取當前路由對應的組件,處理嵌套路由就用不上了
// this.routerMap = {}
// this.$options.router1.forEach(route => {
// this.routerMap[route.path] = route.component
// })
}
onHashChange () {
// window.location.hash就是url中錨部分,但是它以# 開頭,需要把#去掉
this.current = window.location.hash.slice(1)
this.match = []
this.matchRouter()
}
matchRouter (routes) {
// 由於是遞歸,所以需要接收遞歸是傳入的參數,第一次直接取所有的路由表
routes = routes || this.$options.router1
for (const route of routes) {
// 如果是首頁,直接將route push到match數組裏面去
if (route.path === '/' && this.current === '/') {
this.match.push(route)
return
}
if (route.path !== '/' && this.current.indexOf(route.path) !== -1) {
this.match.push(route)
if (route.children) {
this.matchRouter(route.children)
}
return
}
}
}
}
修改後的KRouterView.js
// 這個組件就是獲取當前路由對應的組件,拿到這個組件對象,並渲染到頁面中
export default {
// 處理嵌套路由
render (h) {
let component = null
// this.$vnode是當前組件的虛擬dom,我們在它虛擬dom的data屬性中設置一個自定義的屬性,代表自己是一個routerview
this.$vnode.data.routerView = true
// 需要標記當前路由的深度,循環獲取父組件,如果父組件的routerview爲true,則代表自己的深度加1
let deep = 0
let parent = this.$parent
while (parent) {
const vnodeData = parent.$vnode && parent.$vnode.data
if (vnodeData && vnodeData.routerView) {
deep++
}
parent = parent.$parent
}
// 通過match數組獲取當前的route
const route = this.$router.match[deep]
if (route) {
component = route.component
}
return h(component)
}
}