Vue深入学习系列:vue-router的核心原理学历剖析

  1. Vue Router 是 Vue.js (opens new window)官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:

    • 嵌套的路由/视图表

    • 模块化的、基于组件的路由配置

    • 路由参数、查询、通配符

    • 基于 Vue.js 过渡系统的视图过渡效果

    • 细粒度的导航控制

    • 带有自动激活的 CSS class 的链接

    • HTML5 历史模式或 hash 模式,在 IE9 中自动降级

    • 自定义的滚动条行为

      相信大家写过vue项目,对vue-router这个路由插件不会陌生,具体的使用可以点击下方vue-router官方文档具体学习

      官方文档

      Github地址

  2. 面试中对于vue-router经常会被问到,那他背后的具体实现原理是什么呢? what?最后回答基本都是根据url栏变化,动态渲染对应的组件,可具体是怎么实现的呢,这个也是我最近学习的一个方向,这次简单的实现一个最基本的vue-router插件

  3. 首先,我们知道vue-router有两种模式,分别为history模式,hash模式,SPA单页面在url改变而页面并不刷新的原因就在于这两种模式,今天我们实现hash模式,history以后有机会再实现吧

    • 安装

      vue add vue-router
      复制代码

      我们可以直接使用vue官方脚手架的命令,安装,安装前会改变代码,记得保存哦

    • 正常生成文件夹,其中主要使用到vue-router的文件为

      其中index.js为router文件夹为我们生成的router配置项

      而main.js为打包的入口

      App.vue是对路由的应用,熟悉的味道,熟悉的配方…

      好了,环境准备好了,那就就开始实现插件,刚才在index.js大家也发现了注释掉的my-router.js,主角开始登场.

    • 在我们配置roputer时,根据Vue插件的机制,,在使用Vue.use(‘xxx’)的方法时,会调用插件的install方法,并将Vue的根实例传入,

      我们先准备好基本的VueRouter类

      //声明全局Vue
      let Vue
      // vue-router 类
      class VueRouter{
          constructor(options){
              // 保存配置项,后期给mian.js中挂载的vue调用
              this.options = options
          }
      }
      
      //实现install方法
      VueRouter.install=(_Vue)=>{
          Vue = _Vue
      }
      
      export default VueRouter
      复制代码
    • 基本的写法先解决掉,现在页面应该会报错,我们App.vue中使用,还没有注册定义,那么我们先正常注册两个组件

      // <router-link to="/">Home</router-link> 常用的模式,本质其实是一个a标签,a标签对应的href属性加上'#'
      function createRouterLink() {
        return {
          props: {
            to: {
              type: String,
              required: true,
            },
          },
       // 因为我们执行的是未携带编译器的版本,所以无法解析template,需要使用render函数,使用插槽机制$slots.default渲其中内容
          render(h) {
            return h(
              'a',
              {
                attrs: {
                  href: '#' + this.to,
                },
              },
              this.$slots.default
            )
          },
        }
      }
      复制代码
    • 现在解决 ,路由占位符,那它是怎么渲染其他的组件呢?很好解决,还是万能的render函数,Vue可以直接使用render(‘组件’)的方法,使动态渲染我们需要的那个组件,那么问题来了,我们怎么在install中拿到url对应的变化地址?又哪里得到组件呢?

      //<router-view>
      function createRouterView(){
          return {
              render(h){
                  /*
                  	问题:1.从哪里拿到每次动态变化的url
                  		2.从哪里得到对应的component
                  */
                  h(null)
                  //先使用null,让我们的程序不会报错
              }
          }
      }
      复制代码
    • 其实可以从new VueRouter实例中得到路由表,先从问题1开始,我们知道windo对象的location中是可以直接拿到hash值的,在实例中就可以通过事件获取每次变化的url.

    • 那先从VueRouter类开始

      class VueRouter{
          constructor(options){
              this.options = options
              //获取初始值#后面的path部分
              this.current = window.location.hash.slice(1) || '/'
              //监听hash事件,并每次重新赋值current
              window.addEventListener('hashchange',()=>{
                  this.current = window.location.hash.slice(1)
              })
          }
      }
      复制代码
    • 问题2,在中mian.js中得到router的实例,并可以Vue的$options中,也可以获取到实例属性,这样我们就可以将这一系列代码通过Vue实例,串联起来(好抽象啊T_T),可是又要怎么才能把main.js的Vue实例与install方法串联起来,在什么时候做的?答案就是mixin,我们可以使用mixin在vue初始化的钩子函数中,混入我们的VueRouter实例

      VueRouter.install = function(_Vue){
          //全局定义的Vue
          Vue = _Vue
          
          Vue.mixin({
              // 使用beforeCreate,是在初始化最早的钩子中混入
              beforeCreate(){
                  //只在最开始的根实例中混入一次
                  if(this.$options.router){
                      //这样在每个组件中的$router中都可以拿到VueRouter的实例属性
                      Vue.prototype.$router = this.$options.router
                  }
              }
          })
      }
      复制代码
    • 然后我们就可以完成

      //<router-view>
      function createRouterView(){
          return {
              render(h){
        			const { current , options:{ routes } } = this.$router
                  //取出path映射的component
                  const currentComponent = routes.find(route => route.path === current).component
                  h(currentComponent)
              }
          }
      }
      复制代码
    • nice啊,有事没,没事点两下(意气风发)…啊嘞?emmmmm,怎么url改变了,不渲染对应的组件呢?Vue不是响应式的嘛,嗯?响应式,怎么把这个忘了,在Vue中数据改变对应的视图更新,可是我们的current不是响应式数据,Vue不会执行对应的update函数,那就把current改为响应式数据.怎么变呢?总不能再用Object.defineProperty()做吧,那太麻烦了.如果看过Vue的源码的话,你会知道Vue.util.defineReactive(target,key,val)这个方法,在源码中initState时observe绑定(以后有机会细说)…那我们开始改造代码

      //还记得我们的全局Vue嘛,在下方呗install赋值后,开始它的作用了
      let Vue
      class VueRouter{
          constructor(options){
              this.options = options
              this.current = window.location.hash.slice(1) || '/'
              window.addEventListener('hashchange',()=>{
                  this.current = window.location.hash.slice(1)
              })
              //将current变为响应式数据
              Vue.util.defineReactive(this,'current',this.current)
          }
      }
      复制代码
    • 最最基础的vue-router就实现了

  4. 这就是整体的一套流程梳理

  5. 总结:

    • Vue插件机制,需要实现install方法,install第一个参数为Vue的实例,需要全局保存

    • 使用了组件中的render函数渲染组件,保证代码的正确编译

    • 在VueRouter将url,options保存为自身的实例属性,通过main.js中拿到的VueRouter实例,并获取到router.js的配置,通过Vue.mixin方法在全局混入VueRouter的实例,在第一次初始化的时候混入到Vue根实例的原型中,以便各个组件中调用

    • 使用Vue.util.defineReactive()方法,将url的hash值,变为响应式数据,使url改变时,触发对应的update函数,重新render

本系列为个人平时学习的一个记录,里面还有很多的不足与错误,希望技术好的大佬,可以指出我的错误,还有不足的地方,非常感激,每一个BUG都是我进步的阶梯,欢迎大家一起学习…后续我会持续更新各种系列,纪念第一次写文章的我…希望多年后的自己看到这里,还能还记得现在我对代码,对技术的热情…其中如果有侵权的行为,请联系我…谢谢

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享