vuerouter简单实现

76 阅读1分钟

/**

  • @description 自定义router
  • @author chendada */

let _Vue = null export default class VueRouter { static install (Vue) { // 1. 判断是否注册过,如果已注册过,不再注册 // 2. 缓存当前的Vue构造函数 // 3. 挂载$router到vue实例下,利用混入,混入到每个组件的实例中

if (VueRouter.install.installed) {
  return
}
VueRouter.install.installed = true

_Vue = Vue

// Vue.prototype.$router = this.$options.router
// 为什么不能直接在Vue的原型对象上挂载$router呢,因为在当前函数中,this指向的是并不是vue实例,故this.$options是不存在的
// 故我们通过混入的形式,在vue实例的beforeCreate生命周期中去给vue原型添加$router
Vue.mixin({
  beforeCreate () {
    if (_Vue.prototype.$router) {
      return
    }
    _Vue.prototype.$router = this.$options.router
  }
})

}

constructor (options) { // 实现routerMap(用于存储路由与组件的映射关系) this.routerMap = {} this.createRouteMap(options.routes || [])

// 记录当前路由模式,如果不传模式,默认给history模式
this.mode = options.mode || 'history'

// 定义一个响应式对象
this.data = _Vue.observable({
  current: this.mode === 'history' ? '/' : '#/' // 存放当前url地址
})

// 初始化公用组件
this.initComponents()

// 注册相关事件
this.initEvent()

}

initComponents () { // 初始化router-link组件 this.initLink()

// 初始化router-view组件
this.initView()

}

// 注册相关事件 initEvent () { if (this.mode === 'history') { /** * @description popstate事件,当浏览器历史发生变化时,会触发该事件,主要用于处理当浏览器点击前进后退时, * 触发该事件,去改变current变量的值,从而触发对应组件的重新渲染,从而改变页面 * @ps history模式下,浏览器历史发生变化才会触发popstate事件 */

  // 用户首次打开时,加载当前路由对应的组件
  window.addEventListener('load', () => {
    const path = location.pathname ? location.pathname : '/'
    this.data.current = path
  })
  
  window.addEventListener('popstate', () => {
    this.data.current = location.pathname
  })
} else {
  /**
   * @description hashchange事件,当浏览器url的hash地址发生变化时,会触发该事件,主要用于处理当浏览器url的hash地址发生变化时
   * 触发该事件,去改变current变量的值,从而触发对应组件的重新渲染,从而改变页面
   * @ps hash模式下,浏览器url的hash发生变化时才会触发hashchange事件
   */

  // 页面首次加载时,加载当前路由对应的组件
  window.addEventListener('load', () => {
    // 页面加载时,如果没有hash符,添加hash符
    location.hash = location.hash || '/'
    this.data.current = location.hash
  })
  
  window.addEventListener('hashchange', () => {
    this.data.current = location.hash
  })
}

}

// 注册router-link组件 initLink () { _Vue.component('router-link', { props: { to: String }, render (h) { return h('a', { attrs: { href: this.to }, on: { click: this.locationHref } }, [this.slots.default]) }, methods: { locationHref (e) { if (this.router.mode === 'history') { /** * @description pushState用于改变浏览器跳转地址 * 参数有3个 第一个参数:一个对象,后续触发popState事件时,传给popState的事件的事件对象 * 第二个参数:是title,网页标题 * 第三个参数:需要跳转的url地址 */ history.pushState({}, '', this.to)

        // 更新data下的current变量的值(该变量用于记录当前url地址,当url发生变化时,需要改变这个变量的值)
        // 因current是响应式数据,故当值发生变化时,会触发对应组件的重新渲染,从而当url发生变化时,页面也会发生变化
        this.$router.data.current = this.to
      } else {
        window.location.hash = `#${this.to}`
        this.$router.data.current = `#${this.to}`
      }

      // 阻止a标签默认事件,这里需要阻止a标签的href跳转,因为a标签的href跳转是会让浏览器直接向服务器去发送请求的
      e.preventDefault()
    }
  }
})

}

// 注册router-view组件 initView () { const self = this _Vue.component('router-view', { render (h) { // 从路由表中获取当前path对应的component组件 let component = null if (this.$router.mode === 'history') { component = self.routerMap[self.data.current] } else { // hash模式下时,截取#后面的地址作为path路径,然后再去路由表中匹配对应的组件 const path = self.data.current.slice(1, self.data.current.length) component = self.routerMap[path] } // 渲染对应的组件 return h(component) } }) }

// 解析路由表,得到路由与组件的映射关系 createRouteMap (routes, parentPath) { if (routes && routes.length && routes.length > 0) { routes.forEach((item) => { let cPath = '' if (parentPath) { cPath = ${parentPath}/${item.path} } else { cPath = item.path } this.routerMap[cPath] = item.component if (item.children && item.children.length > 0) { this.createRouteMap(item.children, cPath) } }) } } }