vueRouter实现原理

196 阅读3分钟

Hash模式

  • URL中#后面的内容作为路径地址 // hash值发生改变的时候不会请求后端。
  • 监听hashchange事件
  • 根据当前路由地址找到对应的组件重新渲染。

History模式

  • 通过history.pushState()方法改变地址栏 // 只是改变地址栏地址,改变浏览器访问历史。 兼容IE10及以上
  • 监听popstate事件 // 浏览器历史的变化 back(). go() 会触发。
  • 根据当前路由地址找到对应组件重新渲染

实现自己的vue Router ----准备

Vue.use() // 可以一个函数或者一个对象。

Vue Router -install

let _Vue = null
export default class VueRouter {
  /**
   * @name: install
   * @msg: 静态类
   * @param {Object} Vue
   * @return {null} null
   */
  static install (Vue) {
    // 1. 判断当前插件是否被安装
    if (VueRouter.install.installed) {
      return
    }
    VueRouter.install.installed = true
    // 2. 把Vue构造函数记录到全局变量
    _Vue = Vue

    // 3. 把创建Vue实例时候传入的router对象注入到Vue实例上。
    // _Vue.prototype.$router = this.$options.router
    // 混入
    _Vue.mixin({
      beforeCreate () {
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init()
        }
      }
    })
  }

  /**
   * @name: constructor
   * @msg: 构造函数
   * @param {viod} options
   * @return {null} null
   */
  constructor (options) {
    this.options = options
    this.routeMap = {}
    console.log(_Vue)
    this.data = _Vue.observable({
      current: '/'
    })
  }

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

  /**
   * @name: createRouteMap
   * @msg: options参数遍历到routeMap这个对象里面
   * @param {null} null
   * @return {null} null
   */
  createRouteMap () {
    // 遍历所有的路由规则,把路由规则解析成键值对的形成 储存到routeMap中
    this.options.routes.forEach(route => {
      // 见路由和组件以键值对的方式
      this.routeMap[route.path] = route.component
    })
  }

  /**
   * @name: initComponents
   * @test: test font
   * @msg: 初始化路由组件
   * @param {Object} Vue
   * @return {null} null
   */
  initComponents (Vue) {
    Vue.component('router-link', {
      props: {
        to: String
      },
      /**
       * @name: render
       * @msg: 渲染虚拟dom
       * @param {function} h
       * @return {function} h()
       */
      render (h) {
        /**
         * @name: h
         * @msg: 返回创建好的元素
         * @param {string} 标签如:‘a’ 可以是选择器
         * @param {Object} {}
         * @return {type}
         */
        // 创建的a标签会导致页面刷新,而且会再次请求服务端。
        // 我需要的效果是浏览器地址栏改变且不请求服务端。且页面不刷新。
        // vue监听浏览器地址栏的变化。因为vue是单页面应用。所有组件被render()
        // 创建成vnode,然后根据监听地址栏渲染对应的组件。
        // (createElement: () => VNode) => VNode
        return h('a', {
          attrs: {
            href: this.to
          },
          on: {
            click: this.clickHandler
          }
        }, [this.$slots.default])
      },
      methods: {
        clickHandler (e) {
          // 将当前地址栏改成this.to
          // 将current赋值为this.to找到对应的组件。
          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) {
        // 根据当前浏览器地址获取到对应的组件。将组件交给h()解析出虚拟dom.
        const component = self.routeMap[self.data.current]
        return h(component)
      }
    })
  }
  /**
   * @name: initEvent
   * @msg: 注册popstate事件
   * @param {null} null
   */

  initEvent () {
    window.addEventListener('popstate', () => {
      this.data.current = window.location.pathname
    })
  }
}

vueRouter错误集合

错误一

vuecli创建的vue默认是运行版本的vue.也就是template模版已经编译成render 函数。如果这个时候我们再在组件加入template模版。这个时候vue就没法编译。这个时候我们可以创建一个vue.config.js配置文件。runtimeCompiler: true. 我们还可以使用直接写render方法创建元素。

是否使用包含运行时编译器的 Vue 构建版本。设置为 true 后你就可以在 Vue 组件中使用 template 选项了,但是这会让你的应用额外增加 10kb 左右。

  • 因为在组件内部使用了template.
  • 解决办法一
  • 解决办法二
/**
 * @name: render
 * @msg: 渲染虚拟dom
 * @param {function} h
 * @return {function} h()
 */
render (h) {
  /**
   * @name: h
   * @msg: 返回创建好的元素
   * @param {string} 标签如:‘a’ 可以是选择器
   * @param {Object} {}
   * @return {type}
   */
  return h('a', {
    attrs: {
      href: this.to
    }
  }, [this.$slots.default])
}
  • router-link:
    • active-class 所以to='/*'属性都加上class
    • exact-active-class // 精确匹配