在进行路由创建时,我们需要一个router对所有的路由进行管理工作以及确认采取哪种路由模式。 route为路由对象,它包括path、component、name等
new Router
new router时进行matcher的创建和路由模式的确定
history采用浏览器历史调用栈的方式、 hash模式则使用带#
URL格式
var VueRouter = function VueRouter (options) {
if ( options === void 0 ) options = {};
this.app = null;
this.apps = [];
this.options = options;
this.beforeHooks = [];
this.resolveHooks = [];
this.afterHooks = [];
// matcher对象包含用于路由匹配的方法、还有addRoutes添加路由方法
// 调用该方法里会 进行createRouteMap方法的调用, 生成routeMap
this.matcher = createMatcher(options.routes || [], this);
var mode = options.mode || 'hash';
this.fallback =
mode === 'history' && !supportsPushState && options.fallback !== false;
if (this.fallback) {
mode = 'hash';
}
if (!inBrowser) {
mode = 'abstract';
}
this.mode = mode;
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));
}
}
};
createRouteMap
以下面一个简单的示例为例
const Foo = { template: 'foo' }
const Bar = { template: 'bar' }
const Sub = { template: 'Sub' }
...
const routes = [
{ path: '/foo', component: Foo , children: [
{
path: '/sub',
component: Sub
}
]},
{ path: '/bar', component: Bar }
]
经过createRouteMap后返回的结果如下
createRouteMap将路由对象进行了拍平处理, 生成pathMap映射。
path属性对应当前的路由路径
components当前路由对应的模板
regex进行路径的匹配工作,同时对动态路由进行params的获取
全局挂载
我们知道在使用$router
时可获取到router实例对象, 其实现的代码如下
通过beforeCreate在Vue组件上混入_routerRoot根路由, 方法$router时实际上是通过this._routerRoot._router获取到的。
Vue.mixin({
beforeCreate: function beforeCreate () {
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;
}
registerInstance(this, this);
},
destroyed: function destroyed () {
registerInstance(this);
}
});
Object.defineProperty(Vue.prototype, '$router', {
get: function get () { return this._routerRoot._router }
});
Object.defineProperty(Vue.prototype, '$route', {
get: function get () { return this._routerRoot._route }
});
思考一下在进行路由切换的时候经历了一些什么, 略去细节的话大体如下
1)比如我们会先点击一个link,
2)link组件会触发handler方法
3)进行当前路由属性的变更,
4)路由的变更最终引起view视图更新工作。
更具体一些的实现如下
RouterLink组件
绑定事件方法如下
var handler = function (e) {
if (guardEvent(e)) {
if (this$1.replace) {
router.replace(location, noop);
} else {
router.push(location, noop);
}
}
};
以history模式为例
其中router.push
的调用轨迹 router.push -> history.push -> transitionTo -> confirmTransition
-> runQueue -> onComplete -> updateRoute -> this.cb(route) -> app._route = route;
- router.push中进行了history.push的调用工作。
- history.push获取当前的路由对象作为fromRoute,可在导航守卫中进行使用
- transitionTo为路由切换的入口函数, 不管是link点击、history前进后退或者直接调用push方法最终都是以该方法作为导航切换的入口。
在该函数中会进行route对象的匹配获取, 然后进入到confirmTransition方法 - confirmTransition创建调用队列包括 extractLeaveGuards 、beforeHooks、extractUpdateHooks、activated.、resolveAsyncComponents。
创建一个迭代方法用于队列的调用,队列事件完成后进行回调函数的处理 - onComplete包括 updateRoute、 pushState、afterHooks等方法
- updateRoute会调用history.listen中添加的cb方法从而将app上挂载的_route进行更新,触发set监听和获取当前路由。
pushState最终调用 history.pushState方法向历史堆栈中添加一条状态
RouterView组件
在render的时候从var route = parent.$route;
获取当前的route对象, 该组件是函数式组件会从parent中获取route。
_route属性为响应式的, 当它发生变化会触发view视图的更新。
Object.defineProperty(Vue.prototype, '$route', {
get: function get () { return this._routerRoot._route }
});
_route响应式实现的方法
Vue.util.defineReactive(this, '_route', this._router.history.current);
var route = parent.$route;
...
history.listen(function (route) {
this$1.apps.forEach(function (app) {
app._route = route;
});
});
通过defineReactive, 将_route设置为响应式的。 get进行依赖收集, set时触发view更新。
路由事件绑定及变化过程
var eventType = supportsPushState ? 'popstate' : 'hashchange';
window.addEventListener(
eventType,
handleRoutingEvent
);
this.listeners.push(function () {
window.removeEventListener(eventType, handleRoutingEvent);
});
transitionTo -> (router.match),
updateRoute用于更新当前路由 -> app._route = route;
confirmTransition ->resolveQueue
当活动历史记录条目更改时,将触发popstate事件。需要注意
的是调用history.pushState()或history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮
在使用history模式时需考虑404问题。
url的hash发生变更时会触发 hashchange方法