Appearance
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-view
、router-link
new VueRouter()
对于初始化 VueRouter 实例方法,其主要作用是以几方面:
生成 marcher 对象
根据 mode 去初始化
hash
、history
、hash
三种类型各自的 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
对象