模拟vue-router
实现简单的路由功能, 仿照vue-router
使用的方法, 在router/index.js
中配置路由文件从而逐步实现目标.
实现简易的
PVueRouter
类, 首先实现静态install
方法, 保证Vue.use(Router)
这一步正常运行. 并挂载到组件中的$router属性let Vue // 定义Vue变量,在 install 方法中赋为真正的构造函数使用 class PVueRouter { constructor(options){ this.$options = options // 为了方便,在此处将路由实例赋给Vue原型,这样在路由组件内可以通过 this.$router 访问. 与下面的 mixin 同样作用 Vue.prototype.$router = this } // 静态方法 install static install(_Vue){ Vue = _Vue // 任务1:挂载$router 或者使用 mixin 在根组件混入当前路由实例 Vue.mixin({ beforeCreate() { // 只有根组件拥有router选项 if (this.$options.router) { // vm.$router Vue.prototype.$router = this.$options.router; } } }); Vue.component('router-link', RouterLink) Vue.component('router-view', RouterView) // 首先要定义 router-link 及 router-view 组件 } } // import Router from 'pvue-router' // 引入简易版自定义的router类 // Vue.use(Router)
此时运行会发现报错, 提醒还需要定义
router-link
及router-view
Vue.component('router-link', { props: [ 'to' ], // 无法编译模板,需要使用render函数 <a href="#/about">router-link</a> 或者 jsx // template: '<a>router-link</a>', render(h) { return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default) }, }) Vue.component('router-view', { render(h) { return h(null) } })
定义
current
, 记录当前路由地址. 监听hashchange
事件, 路由变化即时更新从而重新渲染router-view
的内容.class PVueRouter { constructor(options){ this.$options = options Vue.util.defineReactive(this, 'current', window.location.hash.slice(1) || '/') window.addEventListener('hashchange', () => { this.current = window.location.hash.slice(1) || '/' }) this.routeMap = {} this.$options.routes.forEach(route => { this.routeMap[route.path] = route.component }); Vue.prototype.$router = this } Vue.component('router-view', { render(h) { const component = routeMap[current] || null; return h(component); } })
至此完成了简单的路由功能...
但是当出现嵌套路由时, 就会发现 Maximum call stack size exceeded
. 因为内部又嵌套了router-view
, 所以渲染路由对应的组件时会无限套娃......
比如当前路由地址为'/about/info'
, 对应AboutInfo
组件; 其父级为 '/about'
, 对应About
组件. 需要对其进行深度标记, 并使用 matched 代替 current 属性, 因为需要分别渲染2个层级的路由组件.
定义
matched
,存放当前路由匹配的所有层级的组件.// current不再需要响应式数据,只需要及时更新matched数组即可 this.current = window.location.hash.slice(1) || '/' Vue.util.defineReactive(this, 'matched', []) this.match() // hashchange事件中需要再次给 matched 赋值 match(routes){ routes = routes || this.$options.routes for(let route of routes) { // 进行匹配之前, 首先排除'/'根路径的情况 if(route.path === '/' && this.current === '/') { this.matched.push(route) return } // 判断当前路径是否包含了路由文件中的配置 if(route.path !== '/' && this.current.indexOf(route.path) !== -1) { this.matched.push(route) // 递归处理嵌套路由 if(route.children){ this.match(route.children) } return } } }
优化
router-view
中的逻辑, 根据自身深度depth
, 从matched
当中找到对应的组件Vue.component('router-view', { render(h) { // 添加标识,源码中为 this.$vnode.data.routerView = true this.routerView = true let depth = 0, parent = this.$parent while(parent) { const vnodeData = parent if(vnodeData){ if(vnodeData.routerView){ depth++ } } parent = parent.$parent } // const route = this.$router.$options.routes.find(route => route.path === this.$router.current) let component = null const route = this.$router.matched[depth] if(route) component = route.component return h(component) } })
简单记录下学习过程, 其中 注册路由实例为$router属性
、 监听hashchange事件
、 定义matched为响应式数据
、 router-view 深度标识
等是比较重要的点.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。