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.