vue-router source code analysis-initialization and installation

Posted by waynem801 on Fri, 30 Aug 2019 09:19:11 +0200

Vue-router uses demo

<div id="app">
  <h1>Hello App!</h1>
  <p>
    <router-link to="/foo">Go to Foo</router-link>
    <router-link to="/bar">Go to Bar</router-link>
  </p>
  <router-view></router-view>
</div>

const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]
Vue.use(VueRouter)

const router = new VueRouter({
  routes 
})

const app = new Vue({
  router
}).$mount('#app')

What happened to the new VueRouter?

VueRouter is a class exposed by index.js, so let's see what his constructors do:

constructor (options: RouterOptions = {}) {
    this.app = null
    this.apps = []
    this.options = options      // Developer-passed {routes} objects
    this.beforeHooks = []       // beforeEach hook function
    this.resolveHooks = []      // beforeResolve hook function
    this.afterHooks = []        // afterEach hook function
    
    // Generate a matcher object with two methods {addRoutes, match}
    this.matcher = createMatcher(options.routes || [], this)
    
    // Model defaults to hash
    let mode = options.mode || 'hash'
    
    // IE9 automatically demoted to hash mode
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      mode = 'abstract'
    }
    this.mode = mode

    // According to the mode introduced by the developer, different class instances are constructed and assigned to this history.
    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}`)
        }
    }
}

Obviously, constructors mainly do some initialization work of class attributes, including generating matcher objects and initializing corresponding history classes according to mode. We don't explore the significance of these attributes for the time being. We just need to know that developers pass in a {routes} object according to the specification, after a series of operations, I You will get a VueRouter instance router, and then we will pass router into the initialization configuration item of the new Vue to understand the vue's vm.$options Attribute classmates know that we can get router through this.$options.router in the root instance, and the implementation of this step has a lot to do with vue.use(VueRouter).

install function

As we all know, vue.use is the way that Vue uses plug-ins. Vue calls the install method of plug-ins. What about the install function of VueRouter?

notes: For easy reading, Code has been deleted

export function install (Vue) {

  // Repeated Installation Detection
  if (install.installed && _Vue === Vue) return
  install.installed = true
 
  // Tip: _Vue retains the reference to Vue, exports _Vue, and the entire project can use Vue
  _Vue = Vue
  
  // Global Mixing beforeCreate Hook Function
  Vue.mixin({
    ---
  })
  
  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })

  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)
}

The install function does a very important thing, Vue.mixin(...). A hook function is incorporated globally. Every component will go through the hook function once, and what does the hook function do?

beforeCreate () {
  // Initialize router on the root instance and define the response_route
  if (isDef(this.$options.router)) {
    this._routerRoot = this
    this._router = this.$options.router
    this._router.init(this)
    Vue.util.defineReactive(this, '_route', this._router.history.current)
  } else {
    this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
  }
}

We know that only the root instance has this.$options.router, so when the root instance is initialized, if the condition holds, it goes to the code inside:

  • this._routerRoot is the root instance;
  • this._router is router(VueRoute instance);
  • Calling Vue.util.defineReactive registers a responsive attribute_route on the root instance;
  • Call the init method on the router instance (I'll talk about it later).

Next, when other vue instances are initialized, for example, the <App/> component that we normally use does not have a router attribute on its $options, so it goes to the else logic: if there is a parent instance, then this._routerRoot equals this.$parent._routerRoot, if it does not exist, then this._routerRoot points to the current reality. For example, this means that after all instances of _routerRoot have experienced the beforeCreate hook function, the instance's this._routerRoot will point to the root instance's this, that is to say, all instances of this._routerRoot retain a reference to the root instance. What's the use of this? Look at the following code:

  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })

The answer is obvious. Through the Object.defineProperty of es5, we define the value function of $router on Vue.prototype, return the _router on the root instance, that is, the instance router of VueRouter, and also define the value function of $route, which returns the _route on the root instance, so we can use this in any component. You can get the router object for $router, using methods and attributes on router.

Finally, the intall method registers two components, <router-view/> and <router-link/>:

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

Will be continued in the next chapter

createMatcher function realizes analysis.

Topics: Javascript Vue Attribute