简易Vue-Router源码实现

Hash模式

  • 1、hash即URL中#后面的部分。
  • 2、如果网页URL带有hash,页面会定位到id与hash一样的元素的位置,即锚点
  • 3、hash的改变时,页面不会重新加载,会触发hashchange事件,而且也会被记录到浏览器的历史记录中
  • 4、vue-router的hash模式,主要就是通过监听hashchange事件,根据hash值找到对应的组件进行渲染(源码里会先判断浏览器支不支持popstate事件,如果支持,则是通过监听popstate事件,如果不支持,则监听hashchange事件)

History模式

  • 1、通过history.pushState修改页面地址
  • 2、当history改变时会触发popstate事件,所以可以通过监听popstate事件获取路由地址
  • 3、根据路由地址找到对应组件进行渲染

vue-router使用

1、注册vue-router插件

import VueRouter from 'vue-router'
// 注册VueRouter插件
// Vue.use方法 
// 1、如果传入的是方法,则调用传入的方法
// 2、如果传入的是对象,则会调用插件的install静态方法,并传入Vue构造函数
Vue.use(VueRouter)

2、创建Router实例

// 创建路由表
const routes = [
  {
    path: '/home',
    component: Home,
  },
  {
    path: '/about',
    component: About,
  },
]
// 实例化路由对象
const router = new VueRouter({
  mode: 'hash',
  routes
})

3、Vue实例上挂载router实例

// 实例化Vue时,在实例上注册router对象
new Vue({
  render: h => h(App),
  router
}).$mount('#app')

组件中使用

<router-view></router-view>
<router-link to="/home"><route-link>
// 通过this.$router获取路由对象

总结,VueRouter需要做以下这些事情

  • 1、实现install静态方法
  • 2、根据传入的路由配置,生成对应的路由映射
  • 3、给Vue实例挂载router实例
  • 4、注册全局组件<router-view>和<router-link>, router-view组件通过当前url找到对应组件进行渲染,并且url改变时,重新渲染组件,router-link则渲染为a标签
  • 5、通过currentUrl变量保存当前url,并使数据变为响应式
  • 6、监听hashchange或popState事件,浏览器记录改变时重新渲染router-view组件

代码实现

let VueConstructor = null
export default class MyVueRouter {
  static install(Vue) {
    /* 
     1、保存Vue构造函数
     2、在Vue实例挂载 $router实例
     3、注册全局组件<router-view></router-view> 和 <router-link>
    */
    // 判断是否已经执行过install  
    if(MyVueRouter.installed) {
      return
    }
    MyVueRouter.installed = true
    // 1 保存Vue构造函数
    VueConstructor = Vue
    // 2、通过全局混入的方式,在Vue实例挂载 $router实例
    // install执行的时候,Vue还没有实例化,所以通过mixin。在Vue实例化时去挂载router
    // 因为是全局混入,要判断是不是根实例,才需要挂载router
    Vue.mixin({
      beforeCreate() {
        // 只有根实例 才需要挂载$router, 组件不需要执行
        if (this.$options.router) {
          // new Vue(options)时,已经把router对象保存到$options里,所以可以通过$options.router获取
          console.log('this.$options.router==', this.$options.router)
          Vue.prototype.$router = this.$options.router
        }
        
      },
    })
    // 3、注册全局组件<router-view></router-view> 和 <router-link>
    // <router-link to="/home"></router-link>
    Vue.component('router-link', {
      props: {
        to: {
          type: String,
          required: true,
        },
      },
      methods: {
        clickHandler(e) {
          if (this.$router.$options.mode === 'history') {
            // history 通过pushState改变地址,阻止默认行为
            console.log('history模式')
            history.pushState({}, '', this.to)
            this.$router._data.currentUrl = this.to
            e.preventDefault && e.preventDefault()
          }
        }
      },
      render(h) {
        return h(
          'a',
          {
            attrs: {
              href: '#' + this.to,
            },
            on: {
              click: this.clickHandler
            }
          },
          this.$slots.default
        )
      },
    })
    Vue.component('router-view', {
      render(h) {
        const { routesMap, _data } = this.$router
        const component = routesMap[_data.currentUrl]
        console.log('component==', component)
        return h(component)
      },
    })
  }
  constructor(options) {
    /* 
      1、保存配置选项
      2、获取路由映射,可以通过hash获取到对应组件
      3、设置响应式变量currentUrl,保存当前url
      4、监听hashchange事件,hash改变时,同时改变currentUrl
    */
    //  1、保存配置选项
    this.$options = options
    //  2、获取路由映射,可以通过hash获取到对应组件
    this.routesMap = {}
    this.$options.routes.forEach((route) => {
      this.routesMap[route.path] = route.component
    })
    console.log('routesMap===', this.routesMap)
    // 3、设置响应式变量currentUrl,保存当前url
    this._data = VueConstructor.observable({
      currentUrl: '/',
    })
    //  4、监听popstate或hashchange事件,hash改变时,同时改变currentUrl
    // 如果浏览器支持popstate,则监听popstate,不支持使用hashchange
    window.addEventListener('hashchange', () => {
      this._data.currentUrl = window.location.hash.slice(1)
      console.log('this.currentUrl==', this._data.currentUrl)
    })
  }
}

源码实现思路

  • 因为Vue.use方法会调用install静态方法,并传入Vue的构造函数,所以可以在install方法保存Vue构造函数,使VueRouter内部也可以使用到Vue
  • 通过VueRouter.installed判断插件是否已经安装过,安装过的就无需要再次执行install
  • 通过Vue.mixin混入的方式,在Vue实例化时,在beforeCreate生命周期里,给Vue原型挂载router对象Vue.prototype.router = router,这里只需要挂载根实例,组件则不需要挂载,通过options.router判断是否是根实例
  • 注册router-view、router-link全局组件
  • router-view,根据当前url在路由表中找到对应组件,进行渲染。并且当前url改变时,重新渲染。可以通过一响应式变量保存当前url。
  • router-link渲染为a标签,如果是history模式,点击时通过history.pushState改变浏览器记录,并阻止a标签默认行为,防止页面刷新
  • 在VueRouter构造函数中,保存路由配置,并通过传入的路由表生成路由映射
  • 通过变量currentUrl保存当前url,并将currentUrl设置为Vue的响应式变量,让Vue帮助做依赖收集,router-view则通过依赖currentUrl去获取相对应组件,这样currentUrl改变时route-view会重新渲染
  • 监听浏览器popstate事件,url改变时,currentUrl重新设值(hash模式如果浏览器不支持popstate,则监听hashchange)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容

  • 之前用Vue开发单页应用,发现不管路由怎么变化,浏览器地址栏总是会有一个'#'号。 当时检查自己的代码,没有发现请...
    wdapp阅读 1,365评论 0 0
  • 所需的前置知识: 插件 混入 Vue.observable() 插槽 render函数 运行时和完整版的Vue 实...
    CafuChino阅读 244评论 0 1
  • 一、vue-router实现原理 SPA(single page application):单一页面应用程序,只有...
    walycode阅读 1,048评论 1 3
  • 转载自 后端路由简介 路由这个概念最先是后端出现的。在以前用模板引擎开发页面时,经常会看到这样 大致流程可以看成这...
    可爸阅读 383评论 0 0
  • 前端路由定义 在SPA中,路由指的是URL与UI之间的映射,这种映射是单向的,即URL变化引起UI更新(无需刷新界...
    梦一柯南_白玛嘉措阅读 534评论 0 3