先上一段简单的demo,本文根据此demo进行解析
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/home', component: {template: '<div>home</div>'}}
]
})
new Vue({
'el':'#app',
router,
template: `
<div id="app">
<h1>Basic</h1><router-view class="view"></router-view>
</div>
`
})
vue源码解析(五)中介绍过,Vue.use(VueRouter)其实主要调用了VueRouter.install(Vue)方法
function install (Vue) {
//挂载全局的钩子函数到Vue,vue对象初始化会调用下面的函数
Vue.mixin({
beforeCreate: function beforeCreate () {
if (isDef(this.$options.router)) {
// _routerRoot为当前vue对象
this._routerRoot = this;
// _router为new Vue传入的VueRouter对象
this._router = this.$options.router;
//调用 VueRouter.protoype.init,后面介绍
this._router.init(this);
// 设置响应式的_route,this._router.history.current为当前页面的路由信息
Vue.util.defineReactive(this, '_route', this._router.history.current);
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
}
}
});
//全局注册组件
Vue.component('router-view', View);
Vue.component('router-link', Link);
}
// 全局组件router-view的参数
var View = {
name: 'router-view',
functional: true,
props: {
name: {
type: String,
default: 'default'
}
},
render: function render (_, ref){}
}
install方法主要是挂载钩子函数和全局注册组件,全局注册的组件router-view的值如下,
再看一下router = new VueRouter的过程
var VueRouter = function VueRouter (options) {
this.options = options;
//默认使用hash进行前端路由
var mode = options.mode || 'hash';
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base);
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback);
break
}
}
// HashHistory 和 HTML5History都是继承History
var HashHistory = (function (History$$1) {
function HashHistory (router, base, fallback) {
History$$1.call(this, router, base);
}
if ( History$$1 ) HashHistory.__proto__ = History$$1;
HashHistory.prototype = Object.create( History$$1 && History$$1.prototype );
HashHistory.prototype.constructor = HashHistory;
return HashHistory;
}(History));
var History = function History (router, base) {
// VueRouter实例对象
this.router = router;
//base路径
this.base = normalizeBase(base);
//当前路由信息,此时是一个空值
this.current = START;
};
new Vue的过程中会触发挂载的beforeCreate函数,主要是调用了this._router.init(this);
为了更清晰的解析整个流程,假定我们现在访问的页面路径是/home,并且是hash的方式进行路由
VueRouter.prototype.init = function init (app /* Vue component instance */) {
var history = this.history;
if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation());
} else if (history instanceof HashHistory) {
var setupHashListener = function () {
//监听浏览器地址的变更,并调用transitionTo“跳转”到新的路由页面
history.setupListeners();
};
//初始化时调用一次transitionTo,根据当前浏览器地址栏里的path(/home)来激活对应的路由
history.transitionTo(
// 初始化值为“/home”
history.getCurrentLocation(),
setupHashListener
);
}
};
HashHistory.prototype.setupListeners = function setupListeners () {
//监听浏览器地址的变更
window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', function () {
//实现页面内容的变更,getHash()为变更后的hash路径
this$1.transitionTo(getHash())
}
}
//将页面转换到当前真实的路由
History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) {
var this$1 = this;
// 根据location("/home")得到route对象{name: undefined, meta: {…}, path: "/home", hash: "", query: {…}, …}
var route = this.router.match(location, this.current);
//confirmTransition实现比较复杂,本文不做介绍,主要会执行下面的回调函数
this.confirmTransition(route, function () {
//将histoty.current值更新为route
this$1.updateRoute(route);
//执行onComplete(setupHashListener)
onComplete && onComplete(route);
//更新浏览器url地址
this$1.ensureURL();
}
};
function match (){
遍历路由配置(本文只有一项配置{ path: '/home', component: {template: '<div>home</div>'}})
for (var i = 0; i < pathList.length; i++) {
var path = pathList[i];
var record$1 = pathMap[path];
//判断当前路径是否有匹配的路由配置
if (matchRoute(record$1.regex, location.path, location.params)) {
return _createRoute(record$1, location, redirectedFrom)
}
}
// no match
return _createRoute(null, location)
}
function createRoute (record){
var route = {
name: location.name || (record && record.name),
meta: (record && record.meta) || {},
path: location.path || '/',
hash: location.hash || '',
query: query,
params: location.params || {},
fullPath: getFullPath(location, stringifyQuery$$1),
//当前路径匹配的路由配置
matched: record ? formatMatch(record) : []
};
return Object.freeze(route)
}
根据上面的代码逻辑可以分析得出,vue对象初始化时会挂载属性vm._router(记录了整个应用的路由配置信息)和vm._route(记录了当前的路由信息)。vm._route是响应式的,当浏览器路由改变时,vm._route的值也会相应的改变
vm._route的作用是清楚了,但页面内容的变化是怎么实现的呢?下面再介绍下router-view的作用。
Vue源码解析(四)-components组件介绍过,vue初始化时根据template函数生成render函数,本文render函数会调用vm._c('router-view'),_createElement判断router-view是注册过的组件,因此以组件的方式生成vnode,但是router-view生成vnode的过程与Vue源码解析(四)中的方法又有区别
function _createElement(){
//本例tag=‘router-view’,‘router-view’在components属性中注册过,因此以组件的方式生成vnode
if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
//Ctor是router-view的构造函数VueComponent(Vue.component('router-view', View)注册)
vnode = createComponent(Ctor, data, context, children, tag);
}
}
function createComponent (Ctor){
//Ctor 此时已经是构造函数 , 不需要再调用Vue.extend生成
var baseCtor = context.$options._base;
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
// router-view是functional component(见上文图中view的option的值),与用户自定义的component的vnode生成方法有区别
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
//用户自定义component的vnode构造方法
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children } );
}
function createFunctionalComponent (Ctor){
var options = Ctor.options;
//主要是调用View.render方法,前文提到过
var vnode = options.render.call(null, renderContext._c, renderContext);
return vnode;
}
var View = {
name: 'router-view',
functional: true,
props: {
name: {
type: String,
default: 'default'
}
},
render: function render (_, ref) {
//Vue实例化对象vm
var parent = ref.parent;
// vm._route
var route = parent.$route;
var depth = 0;
//上文提到的createRoute中生成的路由匹配信息
var matched = route.matched[depth];
// _createElement方法
var h = parent.$createElement;
// render empty node if no matched route
if (!matched) {
return h()
}
// 本文component为{template: "<div>home</div>", _Ctor: {…}, inject: {…}}
var component = matched.components[name];
//重新调用_createElement,这次是以常规方式生成vnode,后续vnode将渲染成template中的内容
return h(component, data, children)
}
};
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。