Skip to content

VueRouter 初始化过程

首先我们看一个最简单的 Vue.router 的使用方法

js
// 1. Use plugin.
// This installs <router-view> and <router-link>,
// and injects $router and $route to all router-enabled child components
Vue.use(VueRouter);

// 2. Define route components
const Home = { template: '<div>home</div>' };

// 3. Create the router
const router = new VueRouter({
	mode: 'history',
	base: __dirname,
	routes: [{ path: '/', component: Home }],
});

// 4. Create and mount root instance.
// Make sure to inject the router.
// Route components will be rendered inside <router-view>.
new Vue({
	router,
	template: ``,
}).$mount('#app');

可见对于 VueRouter 其初始化的时候分为两个步骤 1. Vue.use(VueRouter) 2. new VueRouter ,那么这两步分别做的什么哪?

Vue.use(VueRouter)

Vue 插件通用调用方式,那么我们就去看 VueRouter 的 index.js 的 VueRouter.install 方法

``

js
export let _Vue;

/**
    1. Vue.use(VueRouter)的执行流程(VueRouter的注册流程)
 * @param {*} Vue
 * @returns
 */
export function install(Vue) {
	// 如果已经执行过Vue.use(VueRouter) return
	if (install.installed && _Vue === Vue) return;

	install.installed = true;
	// 缓存 Vue
	_Vue = Vue;

	const isDef = v => v !== undefined;

	const registerInstance = (vm, callVal) => {
		let i = vm.$options._parentVnode;
		if (isDef(i) && isDef((i = i.data)) && isDef((i = i.registerRouteInstance))) {
			i(vm, callVal);
		}
	};

	// 定义了全局混合方法,使得每一个组件都能获取到  this._routerRoot
	Vue.mixin({
		beforeCreate() {
			// 只有根组件才会 new Vue({ router })
			if (isDef(this.$options.router)) {
				this._routerRoot = this;
				this._router = this.$options.router;
				// 调用 router.init方法
				this._router.init(this);
				// 使得this.$route变成响应式的。 不然初始化以后 修改current即 当前的routeRecord this.$route不会修改
				Vue.util.defineReactive(this, '_route', this._router.history.current);
			} else {
				// 子组件通过 $parent去获取根组件上的 this._routerRoot
				this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
			}
			registerInstance(this, this);
		},
		destroyed() {
			registerInstance(this);
		},
	});

	// 注册两个组件中的变量 this.$router this.$route
	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);

	const strats = Vue.config.optionMergeStrategies;
	// use the same hook merging strategy for route hooks
	strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
}

可以粗略的统计其主要作用分为以下几个:

  • Vue.mixin 定义全局混合方法,使得每一个组件都能获取到 this._routerRoot

  • 在 Vue.prototype 上挂载了 $router,$route两个实例属性

  • 注册了两个组件 router-viewrouter-link

new VueRouter()

对于初始化 VueRouter 实例方法,其主要作用是以几方面:

  • 生成 marcher 对象

  • 根据 mode 去初始化hashhistoryhash三种类型各自的 History 实例方法

  • 在 history 初始化的时候设置各个类型的监听方法

需要注意的是在 history 的初始化过程中只做了监听,那么怎么处理初始化页面的。

这就需要配合我们的 <router-view>组件,请看其源码中的

js
export default {
	name: 'RouterView',
	props: {},
	render(_, { props, children, parent, data }) {
		// attach instance registration hook
		// this will be called in the instance's injected lifecycle hooks
		//  附加实例注册钩子这将在实例注入的生命周期钩子中调用
		data.registerRouteInstance = (vm, val) => {
			// val could be undefined for unregistration
			const current = matched.instances[name];
			if ((val && current !== vm) || (!val && current === vm)) {
				matched.instances[name] = val;
			}
		};

		// also register instance in prepatch hook
		// in case the same component instance is reused across different routes
		//  还可以在prepatch hook中注册实例,以防相同的组件实例在不同的路由中被重用
		(data.hook || (data.hook = {})).prepatch = (_, vnode) => {
			matched.instances[name] = vnode.componentInstance;
		};

		return h(component, data, children);
	},
};

在我们 new App 的时候会将 new VueRouter 的实例赋值给 router 参数,那么对于我们根组件就可以通过 this.$options.router去获取到 VueRouter 的实例对象,而对于子孙组件其this.$options.router为 undefined,那么就会this.$parent && this.$parent._routerRoot获取父组件的 _routerRoot ,通过这样就保证在通过根组件下只执行一次this._router.init(this);其子孙组件通过 this.$parent._routerRoot去获取根组件的_routerRoot对象。

那么我们上面

js
Object.defineProperty(Vue.prototype, '$router', {
	get() {
		return this._routerRoot._router; //  this._routerRoot === 根组件app对象, 子组件的 this.$router -> 根组件 app._router对象
	},
});

// 这一步的作用就是访问

VueRouter.init

对于路由的 init 的过程,其执行的时机在上面详细说明了,这边只说明其主要做的事:

  • history.transitionTo(history.getCurrentLocation()) 初始化执行当前页面路由的跳转

对于这一步详细请见 tranlationTo

  • history.listen(route => {})

主要作用是我们通过跳转到新的路由页面 的时候需要通过 this.cb 去更新我们在组件中访问的 this.$route 对象