Vue-router原理实现

145 阅读1分钟

准备:

插件、slot插槽,混入、render函数、运行时和完整版的Vue

  • Vue-router的核心代码
// 注册插件// Vue.use() 内部调用传入对象的 install 方法
Vue.use(VueRouter) // 创建路由对象
const router=newVueRouter({
 routes: [
    { name: 'home', path: '/', component: homeComponent }
  ]
}) 
// 创建 Vue 实例,注册 router 对象
new Vue({router,render: h=>h(App)})
.$mount('#app')

实现思路

  • 创建 VueRouter 插件,静态方法 install
    • 判断插件是否已经被加载
    • 判断插件是否已经被加载
  • 创建 VueRouter 类
    • 初始化,options、routeMap、app(简化操作,创建 Vue 实例作为响应式数据记录当前路径)
    • initRouteMap() 遍历所有路由信息,把组件和路由的映射记录到 routeMap 对象中
    • 注册 popstate 事件,当路由地址发生变化,重新记录当前的路径
    • 创建 router-link 和 router-view 组件
    • 当路径改变的时候通过当前路径在 routerMap 对象中找到对应的组件,渲染 router-view

代码实现

  • 创建 VueRouter 插件
import Vue from 'vue'
let _Vue = null
export default class VueRouter {
  static install () {
    // 1 判断当前插件是否已经被安装, 安装过就不在安装
    if (VueRouter.install.installed) {
      return
    }
    VueRouter.install.installed = true // installed 记录安装过
    // 2 把Vue的构造啊何时农户记录到全局变量, 因为将来需要在Vue 实例中使用Vue的构造函数例如注册组件等
    _Vue = Vue
    // 3、 吧创建Vue实例是后传入的router对象注入到Vue 实例上
    // _Vue.prototype.$router = this.$router.options
    // 混入
    _Vue.mixin({
      beforeCreate () {
        // 组件不执行, Vue实例不执行
        if (this.$options.router) { // 只有Vue实例才有$options.router属性,组件是没有的router的, 组件不执行
          _Vue.prototype.$router = this.$options.router // 只需要执行一次
          this.$router.init() // 注册组件
        }
      }
    })
  }

  constructor (options) {
    this.options = options
    this.routeMap = {} // 记录键值对, 路由规则转换成path --> compontents
    this.data = _Vue.observable({
      current: '/'
    })
    // 响应式对象, 存储当前的路由对象
  }

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

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

  /**
   * 创建组件 router-link
   * @param {} Vue
   */
  initComponent (Vue) {
    Vue.component('router-link', {
      props: {
        to: String
      },
      /**
       * 渲染函数, 在没有编译器的时候, 需要我们自己写render函数
       * h : 创建虚拟DOM,
       * @param {*} h
       * return 返回h 创建的虚拟DOM
       */
      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 // data是响应式
          e.preventDefault() // a 标签默认 点击跳转, 刷新
        }
      }
      // template: '<a :href="to"><slot></slot></a>' // 运行时版本的vue缺少编译器,编译器的作用就是把templete转换成render函数, 所以不能支持template,
    })
    const self = this
    Vue.component('router-view', {
      render (h) {
        const path = self.data.current // 获取当前地址, data是响应式的, 发生改变, 会自动更新
        const compnent = self.routeMap[path] // 获取路径对应的组件
        return h(compnent)
      }
    })
  }

  /**
   * 当活动历史记录条目更改时,将触发popstate事件。
   * 返回按钮
   */
  initEvent () {
    window.addEventListener('popstate', () => {
      this.data.current = window.location.pathname
    })
  }
}