手动实现简易的VueRouter

核心:

  1. 实现前端路由(通过H5的hiostory.pushState API实现)
  2. 注册为vue插件
  3. 实现原vuerouter插件的router-link和router-view组件
let _Vue = null;
export default class VueRouter {
  // Vue通过Vue.use方法注册插件
  // 该方法支持接收一个参数,类型为函数或者对象
  // 函数则在实例化时直接调用
  // 对象则在实例化时调用其install方法,调用时会将Vue实例作为参数传入
  /**
   * 
   * @param {import('vue').ComponentOptions} Vue 
   * 
   */
  static install (Vue) {
    // 确认是否安装过该插件
    if(this.install.installed) return;
    this.install.installed = true;

    // 将传入的Vue实例转为全局变量,方便后面调用其方法
    _Vue = Vue;
    // 将Vue实例化时传入的router对象绑定到Vue原型的$router上,使Vue的所有组件(Vue实例)都能调用
    // 使用vue混入方法
    _Vue.mixin({
      beforeCreate() {
        if(this.$options.router) {
          // this.$options.router是初始化Vue实例时传入的router参数,也就是VueRouter实例
          _Vue.prototype.$router = this.$options.router;
          // 调用vuerouter对象初始化方法
          this.$router.init()
        }

      }
    })
  }

  /**
   * 
   * @param {Object} options 
   * @property {Object} options
   * @property {Array} options.routes
   * @property {Object} routeMap
   */
  constructor(options) {
    // 接收实例化时传入的routes配置
    this.options = options;
    // 通过方法将传入options解析为{路径:组件}这种键值对的格式来渲染当前路由组件
    this.routeMap = {}
    // 使用Vue提供的响应式对象方法创建响应式对象data用于监听并存储当前路由
    this.data = _Vue.observable({
      current: '/'
    })
  }

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

  // 用于注册和实现router-link和router-view组件
  initComponent() {
    _Vue.component('router-link', {
      props:{
        to: String
      },
      // 使用vue-cli初始化的vue项目默认使用运行时vue,不支持直接使用template,因此使用render函数渲染组件
      render(h) {
        return h('a', {
          attrs: {
            href: this.to
          },
          on: {
            click: this.handleClick
          }
        }, [this.$slots.default])
      },
      methods: {
        /**
         * @param {Event} e
         */
        handleClick(e) {
          // HTML5 新api,在不刷新页面的前提下更改url
          window.history.pushState({}, '', this.to);
          // 同时修改data.current的值,匹配当前组件
          this.$router.data.current = this.to;
          // 阻止a标签刷新页面
          e.preventDefault()
        }
      }
    })

    // Vue实例内部调用方法this指向Vue实例,故提前进行存储
    const self = this;

    _Vue.component('router-view', {
      render(h) {
        // 根据当前data.current的值匹配当前渲染的组件
        const component = self.routeMap[self.data.current]
        return h(component)
      }
    })
  }

  // 用于监听浏览器的popstate事件动态渲染当前路由对应组件
  initEvent() {
    window.addEventListener('popstate', () => {
      this.data.current = window.location.pathname;
    })
  }

  // 根据当前路由将传入的options解析为{路径:组件}这种键值对的格式来渲染当前路由组件
  initRouteMap() {
    this.options.routes.forEach( route => {
      this.routeMap[route.path] = route.component
    })
  }
}

你可能感兴趣的:(手动实现简易的VueRouter)