Vue Router 源码模拟实现

Vue Router实现原理
  • 导入模块 注册插件(Vue.use传入函数时直接调用,传入对象时调用对象install方法)
  • 创建路由对象 new VueRouter 参数是对象
  • 创建Vue实例 加载router
// 注册插件
// vue.use() 内部调用传入对象的install方法
vue.use(vueRouter)

const router = new VueRouter({
    routes: [
        { name: 'home', path: '/', component: homeComponent }
    ]
})
// 创建vue实例,注册router对象
new Vue({
    router,
    render: h => h(App)
}).$mount('#app')
实现思路

VueRouter类

  • options 记录构造函数中传入的对象
  • data 对象 current属性记录当前路由地址 设置为响应式对象,因为路由地址改变对应组件自动更新
  • routeMap 记录路由地址和组件的对应关系
  • Constructor(Options):VueRouter
  • _install(Vue):void 静态方法 实现插件机制
  • init():void 调用下面三个方法
  • initEvent():void 注册pushState事件 用来监听浏览器历史的变化
  • createRouteMap():void 初始化RouteMap 将初始化传入的对象保存到routeMap中
  • initComponents(Vue):void 创建router-view和router-link组件


    image-20210226153507451.png

完整版 vs 运行时版本

// 完整版本 需要编译器
new Vue({
  template: '
{{ hi }}
' }) // 运行时版本 不需要编译器 new Vue({ render (h) { return h('div', this.hi) } })

vue-cli 创建的项目默认使用的是运行时版本的 Vue.js,需修改vue.config.js中的配置

module.exports = {
    runtimeCompiler: true // 设置为 true 后你就可以在 Vue 组件中使用 template 选项
}
Vue Router history模式实现
let _Vue = null
export default class VueRouter {
  static install (Vue) {
    // 1 判断当前插件是否被安装
    if (VueRouter.install.installed) {
      return
    }
    VueRouter.install.installed = true
    // 2 把Vue的构造函数记录在全局
    _Vue = Vue
    // 3 把创建Vue的实例传入的router对象注入到Vue实例 能获取到Vue实例的时候再注入
    // _Vue.prototype.$router = this.$options.router
    _Vue.mixin({
      beforeCreate () {
        // this就是vue实例 判断不是组件时再挂载,组件上没有router
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
        }
      }
    })
  }

  constructor (options) {
    this.options = options
    this.routeMap = {}
    // observable 设置为响应式
    this.data = _Vue.observable({
      current: '/' // 记录当前路由地址
    })
    this.init()
  }

  init () {
    this.createRouteMap()
    this.initComponent(_Vue)
    this.initEvent()
  }

  createRouteMap () {
    // 遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component
    })
  }

  initComponent (Vue) {
    Vue.component('router-link', {
      props: {
        to: String
      },
      render (h) {
        return h(
          'a',
          {
            attrs: {
              href: this.to
            },
            on: {
              click: this.clickhander
            }
          },
          [this.$slots.default]
        )
      },
      methods: {
        clickhander (e) {
          history.pushState({}, '', this.to)
          this.$router.data.current = this.to
          e.preventDefault()
        }
      }
      // template:''
    })
    const self = this
    Vue.component('router-view', {
      render (h) {
        // self.data.current
        // 当前路由地址对应的组件
        const cm = self.routeMap[self.data.current]
        return h(cm)
      }
    })
  }

  initEvent () {
    // 监听历史记录变化popstate
    window.addEventListener('popstate', () => {
      this.data.current = window.location.pathname
    })
  }
}
Vue Router hash模式实现
let _vue = null
export default class VueRouter {
  static install (vue) {
    // 如果插件安装直接返回
    if (VueRouter.install.installed && _vue === vue) return
    VueRouter.install.installed = true

    // 记录vue构造函数
    _vue = vue

    // 把创建Vue的实例传入的router对象注入到Vue实例,能获取到Vue实例的时候再注入
    _vue.mixin({
      beforeCreate () {
        // 判断vue实例上是否有router对象
        if (this.$options.router) {
          // 把router对象注入到vue实例上
          _vue.prototype.$router = this.$options.router
          // 初始化插件时调用init
          this.$options.router.init()
        }
      }
    })
  }

  // 构造函数
  constructor (options) {
    this.options = options
    // 记录路径和组件的对应
    this.routeMap = {}
    // data需要设置为响应式 observable
    this.data = _vue.observable({
      current: '/' // 记录当前路由地址
    })
  }

  init () {
    this.initRouteMap()
    this.initComponent(_vue)
    this.initEvent()
  }

  // 遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
  // routes => [{ name: '', path: '', component: }]
  initRouteMap () {
    this.options.routes.forEach((route) => {
      this.routeMap[route.path] = route.component
    })
  }

  // 生成 router-link 和 router-view 组件
  initComponent (Vue) {
    Vue.component('RouterLink', {
      props: {
        to: String
      },
      // 需要带编译器版本的 Vue.js
      // template: ""
      // 使用运行时版本的 Vue.js
      render (h) {
        return h(
          'a',
          {
            attrs: {
              href: '#' + this.to
            },
            on: {
              click: this.clickHandler
            }
          },
          [this.$slots.default]
        )
      },
      methods: {
        clickHandler (e) {
          window.location.hash = '#' + this.to
          e.preventDefault()
        }
      }
    })

    const self = this
    _vue.component('RouterView', {
      render (h) {
        const component = self.routeMap[self.data.current]
        return h(component)
      }
    })
  }

  // 监听历史记录变化
  initEvent () {
    window.addEventListener('hashchange', this.onHashChange.bind(this))
    window.addEventListener('load', this.onHashChange.bind(this))
  }

  onHashChange () {
    this.data.current = window.location.hash.substr(1) || '#/'
  }
}

你可能感兴趣的:(Vue Router 源码模拟实现)