模拟vue-router实现简单的路由功能, 仿照vue-router使用的方法, 在router/index.js中配置路由文件从而逐步实现目标.

  1. 实现简易的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)
  2. 此时运行会发现报错, 提醒还需要定义 router-linkrouter-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)
             }     
         })
    
  3. 定义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个层级的路由组件.

  1. 定义 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 
             }
         }
     }       
  2. 优化 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 深度标识 等是比较重要的点.


populus
291 声望2 粉丝

不断学习、努力工作、热爱技术


引用和评论

0 条评论