# vue-router 实现原理

vue-router 实例化时会初始化 this.history,不同 mode 对应不同的 history

constructor (options: RouterOptions = {}) {
    this.mode = mode
    
    switch (mode) {
      case 'history':
        this.history = new HTML5History(this, options.base)
        break
      case 'hash':
        this.history = new HashHistory(this, options.base, this.fallback)
        break
      case 'abstract':
        this.history = new AbstractHistory(this, options.base)
        break
      default:
        if (process.env.NODE_ENV !== 'production') {
          assert(false, `invalid mode: ${mode}`)
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

这里以 HashHistory 为例,vue-router 的 push 方法实现如下:

push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    // $flow-disable-line
    if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        this.history.push(location, resolve, reject)
      })
    } else {
      this.history.push(location, onComplete, onAbort)
    }
}
1
2
3
4
5
6
7
8
9
10

HashHistory 具体实现了 push 方法:


function pushHash (path) {
  if (supportsPushState) {
    pushState(getUrl(path))
  } else {
    window.location.hash = path
  }
}
1
2
3
4
5
6
7
8

对路由的监听通过 hash 相应的事件监听实现:

window.addEventListener(
  supportsPushState ? 'popstate' : 'hashchange',
  () => {
    const current = this.current
    if (!ensureSlash()) {
      return
    }
    this.transitionTo(getHash(), route => {
      if (supportsScroll) {
        handleScroll(this.router, route, current, true)
      }
      if (!supportsPushState) {
        replaceHash(route.fullPath)
      }
    })
  }
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

除此之外,vue-router 还提供了两个组件:

Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
1
2

# vue-router 路由守卫

创建 router.js:

import Vue from 'vue'
import Route from 'vue-router'
import HelloWorld from './components/HelloWorld'

Vue.use(Route)
const routes = [
  { path: '/hello-world', component: HelloWorld }
]
const router = new Route({
  routes
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13

在 main.js 中引用 router,并加入 vue 实例:

import router from './router'

new Vue({
  render: h => h(App),
  router
}).$mount('#app')
1
2
3
4
5
6

# 全局守卫

router.beforeEach((to, from, next) => {
  console.log('beforeEach', to, from)
  next()
})

router.beforeResolve((to, from, next) => {
  console.log('beforeResolve', to, from)
  next()
})

router.afterEach((to, from) => {
  console.log('afterEach', to, from)
})
1
2
3
4
5
6
7
8
9
10
11
12
13

# 局部守卫

beforeRouteEnter (to, from, next) {
  // 不能获取组件实例 `this`
  console.log('beforeRouteEnter', to, from)
  next()
},
beforeRouteUpdate (to, from, next) {
  console.log('beforeRouteUpdate', to, from)
  next()
},
beforeRouteLeave (to, from, next) {
  console.log('beforeRouteLeave', to, from)
  next()
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 路由元信息

通过 meta 定义路由元信息

const routes = [
  { path: '/a', component: A, meta: { title: 'Custom Title A' } }
]
1
2
3

使用 meta 信息动态修改标题

router.beforeEach((to, from, next) => {
  console.log('beforeEach', to, from)
  if (to.meta && to.meta.title) {
    document.title = to.meta.title
  } else {
    document.title = 'default title'
  }
  next()
})
1
2
3
4
5
6
7
8
9

# 路由 API

使用 router.addRoutes 动态添加路由

addRoute() {
    this.$router.addRoutes([{
      path: '/b', component: B, meta: { title: 'Custom Title B' },
    }])
}
1
2
3
4
5

此时可以访问到 B 组件

<router-link to='/b'>to B</router-link>
1