A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 大蓝鲸小蟀锅 于 2020-3-26 22:48 编辑

      大名鼎鼎的Vue Router是Vue官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当你要把 Vue Router 添加进来,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们,使用步骤特别清晰。
     使用步骤分成template部分和js部分
     template部分:
     1、使用router-link组件来导航,通过传入to属性指定链接(默认会被渲染成一个<a>标签)
     2、使用router-view组件来占位将来路由匹配到的组件,会被渲染到这里
      js部分:
      1、导入Vue和vue-router(命名成VueRouter)、要调用Vue.use(VueRouter)
      2、定义路由表,每一个路由至少包含path和component两个属性,前者表示将来浏览器要被匹配到的路径,后者表示正常匹配到该path以后要渲染的组件(当然,还可以指定路由模式为hash或者history,不指定的话默认为hash)
      3、创建一个路由VueRouter实例,将先前创建好的路由表置于该实例当中
      4、将第3步创建好的路由实例挂载到根实例上,属性为router,一般取路由实例的名称也叫router,因为挂载的时候可以使用es6中对象的简写形式(key和value同名时,可只写其中一个)

       以上便是使用vue-router的使用步骤,当然还有很多其他功能,比如:动态路由匹配、嵌套路由、编程式导航、命名路由等其他特性
具体可移步至官方文档Vue Router

        下面我们尝试着自己实现一个简易的vue-router:
        分析:官方的vue-router提供了哪些重要东西?
        比如:有两种路由模式可供选择(hash和history)、提供了两个全局组件router-link、router-view、每一个组件中都可以使用this.$router和this.$route这两个属性、并且最重要的是使用的时候需要调用Vue的use方法(查阅文档得知,是默认需要调用vue-router的install方法)

[JavaScript] 纯文本查看 复制代码
class VueRouter {
     constructor({ mode, routes }) {
          this.mode = mode || 'hash'
          this.routes = routes || []
          // 格式化路由表 [] => {}
          // [{path: '/home/, component: Home},{path: }] => {'/home': Home, '/about': About}
          this.routesMap = this.createRoutesMap(this.routes)
          // 需要记录一下当前路由状态
          this.history = { current: null }
          this.init()
     }

     init() {
          // 判断模式
          if (this.mode === 'hash') {
               location.hash ? '' : location.hash = '#/'
               window.addEventListener('load', () => {
                    this.history.current = location.hash.slice(1)
               })
               window.addEventListener('hashchange', () => {
                    this.history.current = location.hash.slice(1)
                    console.log('this.history', this.history)
               })
          } else {
               location.pathname ? '' : location.pathname = '/'
               window.addEventListener('load', () => {
                    console.log('load')
                    this.history.current = location.pathname
               })
               window.addEventListener('popstate', () => {
                    this.history.current = location.pathname
               })
          }
     }

     // 格式化路由表
     createRoutesMap(routes) {
          return routes.reduce((prev, current) => {
               prev[current.path] = current.component
               return prev
          }, {})
     }
}

// 使用use就会调用install方法
VueRouter.install = function (Vue) {
     console.log('install')
     // 每个组件都router得有 this.$router this.$route
     // 在所有组件中如何拿到同一个路由的实例
     Vue.mixin({
          beforeCreate() {
               if (this.$options && this.$options.router) {
                    // 根组件
                    this._root = this // 缓存一下当前实例
                    this._router = this.$options.router // 获取当前路由实例
                    Vue.util.defineReactive(this, 'abc', this._router.history)
               } else {
                    // 父 -> 子 -> 孙
                    this._root = this.$parent._root
               }

               Object.defineProperty(this, '$router', { // VueRouter的实例
                    get() {
                         return this._root._router
                    }
               })

               Object.defineProperty(this, '$route', {
                    get() {
                         // 返回当前页面的状态
                         return {
                              current: this._root._router.history.current
                         }
                    }
               })
          }
     })

     Vue.component('router-link', {
          props: {
               to: {
                    type: String,
                    value: '/'
               },
               // tag: {
               //      String
               // }
          },
          render(h) {
               let mode = this._self._root._router.mode
               // console.log(this._self._root)
               // let tag = this.tag
               return (
                    <a href={mode === 'hash' ? `#${this.to}` : this.to}>
                         {
                              this.$slots.default
                         }
                    </a>
               )
          }
     })
     Vue.component('router-view', {
          render(h) {
               let current = this._self._root._router.history.current
               let routesMap = this._self._root._router.routesMap
               return h(routesMap[current])
          }
     })
}

export default VueRouter




    代码分析:

       1、既然能通过new的形式实例化一个实例,咱们就可以使用类的形式创建一个VueRouter类(当然,构造函数的形式也是可以的)、根据编写插件的要求必须要提供一个install方法,可以直接在类上定义一个install方法,该方法调用时会将Vue当做参数传入;倘若你想传多个参数,那也是可以的
       2、用户使用时可能会传递mode和routes等属性(暂时考虑这两个)用来指明当前路由模式和路由规则(默认是hash模式),VueRouter类里面可以通过构造函数constructor接受mode和routes,以上代码是通过对参数解构,直接拿到mode和routes,而后直接将这两个属性置于当前类的私有属性上
       3、对用户传入的路由规则进行格式化,格式化成一个对象的形式 -> { '/home': Home, '/about': About } ,方便后面直接根据浏览器上的路径匹配到当前组件,不至于后面还要遍历routes数组取组件更麻烦一些
       4、初始化当前路由的状态(当前浏览器的访问路径是谁,就保存谁)
       5、开始对页面一加载时进行判断,如果是hash模式时判断当前浏览器路径是否带有#/,如果没有则自行加上;如果是history时判断当前浏览器路径是否带有/,如果没有也默认加上/;并且是hash模式时添加了hashchange事件,是history时,添加了popstate事件,都是用来监听用户操作的行为,并且更新当前路由的状态,也就是第四步初始了的状态。
      6、install方法里要做一些混合操作,因为每个组件里面都有$router和$route这两个属性,可以看出是定义在全局上(当前this上的),在beforeCreate方法里通过Object.defineProperty方法在this上添加$router和$route这两个属性。
      7、在混入的beforeCreate方法里可以通过this.$options.router判断当前组件是根实例(因为只有根实例上才会注册router属性),而后,缓存一下当前实例到_root上,缓存当前路由实例到_router上,后面其他组件可以通过$parent._root获取到当前路由实例;综上,在定义$router的get方法里可以返回this._root._router(这就是当前的VueRouter实例,上面包含一些push、go、back方法等),在定义$route的get方法里可以返回当前实例上的当前状态等属性(通过this._root_router.history.current)
      8、两个全局组件,可以通过Vue.component的形式创建,同时可以使用render函数返回组件的UI。其中,router-view中的render需要先获取当前路由状态和格式化后的路由表对象,最后通过h(createElement函数)渲染当前状态所对应的组件;router-link就比较简单了,直接返回一个a标签,其中要根据props接受用户传递过来的to属性,根据路由模式判断href中的属性是否带#,并且通过this.$slot.default获取到标签内的文本内容,这里面的写法很类似react,需要添加一对大括号来嵌入js表达式
     9、第56行代码,需要通过Vue.util.defineReactive为当前路由的history添加响应式,因为router-view中需要获取到当前路由状态和当前格式化后的路由对象。因为,根据事件循环机制,load监听事件是慢于install方法的,避免先加载的地方取不到后加载的属性而报错问题。
     10、使用es6Module规范,默认导出当前VueRouter即可


     以上便是自己实现一个VueRouter的大致代码和代码分析,有兴趣的同学可以把代码拿到本地跑一下,切身体验一下。
      其中需要本身具备的知识点有:两种路由模式实现的原理、es6 api的熟练掌握、vue里面高级api的掌握(混合和全局定义组件render方法的编写)
      

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马