实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,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)
}
}