muwoo / blogs

📚一个前端的博客。
2.32k stars 352 forks source link

vue-router 实现 -- new VueRouter(options) #24

Open muwoo opened 6 years ago

muwoo commented 6 years ago

为了构造出 router 对象,我们还需要对VueRouter进行实例化的操作,比如这样:

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '/', name: 'home', component: Home },
    { path: '/foo', name: 'foo', component: Foo },
    { path: '/bar/:id', name: 'bar', component: Bar }
  ]
})

constructor

我们来看一下在VueRouter内部的源码定义:

export default class VueRouter {

  // ...
  constructor (options: RouterOptions = {}) {
    this.app = null
    this.apps = []
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    this.matcher = createMatcher(options.routes || [], this)

    let mode = options.mode || 'hash'
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      mode = 'abstract'
    }
    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}`)
        }
    }
  }

  match (
    raw: RawLocation,
    current?: Route,
    redirectedFrom?: Location
  ): Route {
    return this.matcher.match(raw, current, redirectedFrom)
  }

  get currentRoute (): ?Route {
    return this.history && this.history.current
  }

  init () {}
  beforeEach () {}
  beforeResolve () {}
  afterEach () {}
  onReady () {}
  onError () {}
  push () {}
  replace () { }
  go () {}
  back () { }
  forward () { }
  getMatchedComponents () { }
  resolve ( ) { }
  addRoutes () { }
}

这里我们忽略了大部分的函数实现,后面我么再展开来看。先来看一下constructor实例化的时候将会做的处理:通过new VueRouter({...})我们创建了一个VueRouter 的实例。VueRouter中通过参数mode来指定路由模式,前面已经简单的了解了一下前端路由的2种模式。通过上面的代码,我们可以看出来 VueRouter对不同模式的实现大致是这样的:

  1. 首先根据mode来确定所选的模式,如果当前环境不支持history模式,会强制切换到hash模式;
  2. 如果当前环境不是浏览器环境,会切换到abstract模式下。然后再根据不同模式来生成不同的history操作对象。

由于上篇文章已经介绍了在 install 的过程中,会执行改对象的 init 函数。我们接下来的主要任务就是分析init 的实现。

init

  init (app: any /* Vue component instance */) {
    // ...
    this.apps.push(app)

    // main app already initialized.
    if (this.app) {
      return
    }

    this.app = app

    const history = this.history

    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      const setupHashListener = () => {
        history.setupListeners()
      }
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }

    history.listen(route => {
      this.apps.forEach((app) => {
        app._route = route
      })
    })
  }

回顾一下在 inistall 的 beforCreate 钩子内,我们通过这种方式调用了实例的init方法:

this._router.init(this)

然后我们来分析一下执行的大致过程:init 方法内的 app变量便是存储的当前的vue实例的this。然后将 app 存入数组apps中。通过this.app判断是实例否已经被初始化。然后通过history来确定不同路由的切换动作动作history.transitionTo。最后通过history.listen来注册路由变化的响应回调。 接下来我们就要了解一下 history.transitionTo的主要流程以及 history.listen的实现。当然最基础的是先明白history是个什么东西。接下来我们会分别介绍不同mode下的 history 的实现。