Vue Router 源码模拟实现

2021-02-28  本文已影响0人  翔子丶
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类

完整版 vs 运行时版本

// 完整版本 需要编译器
new Vue({
  template: '<div>{{ hi }}</div>'
})

// 运行时版本 不需要编译器
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:'<a :href='to'><slot></slot></a>'
    })
    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: "<a :href='\"#\" + to'><slot></slot></a>"
      // 使用运行时版本的 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) || '#/'
  }
}
上一篇 下一篇

猜你喜欢

热点阅读