Vue-Router万万没想到你是这样子的一个东西

Vue-Router万万没想到你是这样子的一个东西

我们用了这么久的vue以及其他的vue生态成员后,除了感叹写这个玩意的人真**强大之外,有没有深入的理解过它到底是怎样做得这么强大的,在我的掉头发研究中,我今天给大家分享一下我其中一份研究笔记(其实就是本人的学习笔记,哈哈)
Vue-router作为vue的生态圈成员之一,在我的理解里,它是作为vue的一个插件存在的,所以插件肯定存在一个install方法来进行插件的安装,下面我们现在简单的实现一个属于自己的router插件,下面说一下router插件实现的步骤
Router的实现思路

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Anoos2gM-1583676924366)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200308210731536.png)]

Vue-Router里面有两大核心的组件,router-link和router-view,这两个组件一个是改变路由地址的值,一个是显示的视图,通过link去改变url,然后view通过url显示不同的组件是现在router的主要思路,讲完思路,下面就用代码来实现一个router

跟着我左手右手一个慢动作
  • 创建一个router类

    这里我们新建一个router.js文件

    // 创建一个router的实例
    class VueRouter {
        constructor (options){
            // 私有化变量
            this.$options = options
        }
    }
    

    这里我们创建一个VueRouter的类,为了我们使用new Router()

  • 实现router类的挂载

    let Vue // 预留一个私自变量存传进来的Vue实例 不直接使用防止造成污染
    
    // 实现插件install方法 注册一个router
    VueRouter.install = function(_Vue){
        // 保存传进来的Vue函数
        Vue = _Vue
        // 通过混合的方式才可以获得Vue实例
        Vue.mixin({
            beforeCreate () {
                // 这里options是new Vue({})传入的配置
                // 确保Vue的根实例上有router的参数 才挂载router
                // 如果没有传入router的话 就不会进行挂载
                if(this.$options.router){
                    Vue.prototype.$router = this.$options.router
                }
            }
        })
    }
    

    router作为一个插件的存在,必须拥有install方法才可以use,这里我们通过Vue.mixin来获取Vue的实例进行路由的挂载,因为在我们的router里面,是先执行Vue.use的方法再进行new Router配置,如果不通过混入的方式,没法获取Vue的实例进行挂载

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWaVd2Gl-1583676924369)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200305225508009.png)]

    当我们写到这一步,在文件中引入自己的router,router可以执行并挂载了,但是会发现会有两个报错

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T18oJyuh-1583676924370)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200305225631156.png)]

    因为我们的router里面,并没有router-view和router-link两个组件,接下来我们去创建它们

  • 创建router类的子组件

    实现一个router-link的组件

    其实router-link就是一个a标签,使用render函数创建一个dom对象,接受一个to的参数为路由的导航,

    export default {
      props: {
        to: {
          type: String,
          required: true
        }
      },
      render(h) {
        return h('a', {
          attrs: {
              // 配置url
            href: '#' + this.to
          }
        }, [
            // 这里为子节点为任何中间的东西
          this.$slots.default
        ])
      }
    }
    

    实现一个router-view组件

    router-view组件讲到底,功能就是根据不同的路由显示不同的组件,跟一些tabs类似

    这里我们在实现router-view之前,要想办法去监听当先url的变化,来返回不同的组件,我们修改一下VueRouter类,在里面添加上url的监听事件

    // 创建一个router的实例
    class VueRouter {
      constructor(options) {
        // 利用Vue提供给我们的响应式方法,来监听当前路由地址
        // 初始化路由
        // 定义响应式的current
        // 初始话路由的值 利用浏览器api获取当前的路由
        const initial = window.location.hash.slice(1) || '/'
        Vue.util.defineReactive(this, 'current', initial)
    
        // 监听hashchange事件
        // 避免用户刷新路由的初始化
        window.addEventListener('hashchange', this.onHashChange.bind(this))
        window.addEventListener('load', this.onHashChange.bind(this))
      
        // 私有化变量
        this.$options = options
      }
    	// 路由变化函数
      onHashChange(){
        this.current = window.location.hash.slcie(1)
      }
    }
    

    在监听url的时候,我们用到里Vue提供给我的响应式方法,这就是Vue-router和Vue的存在关系,Vue-Router只能在Vue中才能发挥作用。我们已经在VueRouter类里面监听了路由的变化了,我们就可以在router-view组件里面根据不同的url来返回不同的组件了

    创建一个router-view.js文件

    export default {
      render(h){
          // 初始化一个组件变量
        let component = null
        // 通过遍历用户传进来options配置里面的routes信息,我们可以过去到用户的路由表
        this.$router.$options.routes.forEach(route=>{
            // 如果当前的监听到的url等于用户表里的url,就返回用户配置的组件
          if(route.path === this.$router.current){
            component = route.component
          }
        })
        return h(component)
      }
    }
    

    在router-view组件里面,我们用过遍历路由表的方法,来匹配路由的组件,这样做我们就可以在路由变化的时候,返回一个正确的组件(这样子的遍历性能偏低,因为每一次路由的变化都要重新遍历一次路由表,后面我们会改进)

    然后我们在刚刚VueRouter的install方法里面对两个组件进行注册

    // 实现插件install方法 注册一个router
    VueRouter.install = function (_Vue) {
      // 保存构造函数
      Vue = _Vue
      // 通过混合的方式获得实例
      Vue.mixin({
        beforeCreate() {
          // 这里options是new Vue({})传入的配置
          // 确保Vue的根实例上有router的参数 才挂载router
          // 如果没有传入router的话 就不会进行挂载
          if (this.$options.router) {
            Vue.prototype.$router = this.$options.router
          }
        }
      })
        // 引入它们 并注册
      Vue.component('router-link', routerLink)
      Vue.component('router-view', routerView)
    }
    

    然后你会发现,你的页面已经没有报错了,并且可以正常的使用自己编写的router了,而且很完美,是不是很棒

    优化我们自己的router类

    在刚刚实现router-view组件的时候,我们匹配组件的方式是通过不断的遍历,这样子在少路由的情况下没什么问题,但在路由多的情况下,这样子会造成不必要的性能损失,那我们怎么处理这个问题呢,这里我们可以先提前的处理好我们的匹配方式

  • 提前处理路由表

    class VueRouter {
      constructor(options) {
    
        // 在初始化类的时候,缓存好path和route的对应关系
        this.routeMap = {}
        this.$options.routes.forEach(route=>{
          this.routeMap[route.path] = route
        })
      }
    }
    

    在router类创建的时候初始化一个对应关系

    然后在router-view组件里直接通过对应关系直接拿取组件

    export default {
      render(h){
        // 获取对应表和当前路由
        const {routeMap, current} = this.$router
        // 获取对应组件
        const component = routeMap[current].component
        return h(component)
      }
    }
    

    这样子我们就不用每次都去遍历路由表来获取组件了

致命问题

​ 上面的router类,如果技术高超的人,就会发现一个问题,这样子的router,如果一有嵌套路由,不就全完蛋了吗,接下来,我来解决一下嵌到路由的问题。

https://github.com/vuejs/vue-router/blob/dev/src/components/view.js

通过官方的router.js源码,我们可以看到官方的实现方法是,给router-view组件添加深度标签,然后遍历,我这里依照它的思路,勉强的去解决一下嵌套路由,当然肯定没有官方这么标准

  • 实现的思路
  1. 给router-view组件打上一个识别标签,证明是router-view
  2. 在用遍历来确定嵌套路由的深度
  3. 通过一个匹配方法来形成一个路由的数组,由0开始输入路由组件
  4. 监控url变化后重新匹配

修改一下router-view组件

export default {
  render(h){
    // 添加深度组件标志
    // 这里可以通过在VueRouter的实例对象上添加一个属性
    this.$vnode.data.routerView = true

    // 初始化当前深度为0
    let depth = 0
    // 获取当前组件的父组件
    let parent = this.$parent
    // 如果有父组件 我们就遍历
    // 通过循环得到最终路由的深度
    while(parent){
      // 父组件是否存在data
      const vData = parent.$vnode && parent.$vnode.data
      // 若存在
      if(vData){
        // 判断深度组件标签是否存在
        if(vData.routerView){
          // 存在即证明是一层嵌套 深度加1
         depth++ 
        }
      }
      parent = parent.$parent
    }
}

然后在router类里定义match方法和matched数组

class VueRouter {
  constructor(options) {

    // 私有化变量
    this.$options = options

    // 利用Vue提供给我们的响应式方法,来监听当前路由地址
    // 初始化路由
    // 定义响应式的current
    // 初始话路由的值 利用浏览器api获取当前的路由
    this.current = window.location.hash.slice(1) || '/'
    // 定义一个路由匹配数组
    Vue.util.defineReactive(this, 'matched', [])
    this.match()

    // 监听hashchange事件
    // 避免用户刷新路由的初始化
    window.addEventListener('hashchange', this.onHashChange.bind(this))
    window.addEventListener('load', this.onHashChange.bind(this))
  }
  // 每次路由变化的函数
  onHashChange(){
    this.current = window.location.hash.slice(1)
      // 清空路由数组,重新遍历
    this.matched = []
    this.match()
  }
   // 路由遍历,形成路由数组
  match(routes){
    routes = routes || this.$options.routes
    for(const route of routes ){
      // 若为首页
      if(route.path === '/' && this.current === '/'){
        this.matched.push(route)
        return
      }
      // 例如home/aaa  home是包含在home/aaa 中
      if(route.path !== '/' && this.current.indexOf(route.path) != -1){
        this.matched.push(route)
          // 判断是否为子路由
        if(route.children){
          // 有子路由继续递归
          this.match(route.children)
        }
        return
      }
    }
  }
}

在修改一下router-view组件

    let component = null
    const route = this.$router.matched[depth]
    if(route){
      component = route.component
    }
return component

修改后的router类,通过match方法递归遍历routes配置,得到matched数组,matched数组是当前路由所形成的数组,例如home/aaa 形成的数组就是[home, aaa], 这样子router-view组件可以通过matched数组以及当前的深度来当前路由的组件,就能实现嵌套路由了

最后

亲爱的读者们,以上就是我学习研究vue-router之后想要给大家分享的知识,希望能帮助到各位想要学习的人,本人也是在前端学习的道路上程序员,天下程序员是一家,希望大家一起进步,觉得这篇帮助到你的可以给个👍哦

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值