VueRouter源码浅析
程序员文章站
2022-03-25 10:25:33
...
路由原理
前端路由原理主要是监听浏览器的url变化来达到根据不同url渲染不同页面的目的。
VueRouter主要有三种实现方式:
- history模式
- hash模式
- abstract模式
本文主要讨论hash模式。
使用
先看VueRouter的构造函数
var VueRouter = function VueRouter (options) {
if ( options === void 0 ) options = {};
this.app = null;
this.apps = [];
this.options = options;//接收一个配置对象
this.beforeHooks = [];
this.resolveHooks = [];
this.afterHooks = [];
this.matcher = createMatcher(options.routes || [], this);
var mode = options.mode || 'hash';//不传入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:
{
assert(false, ("invalid mode: " + mode));
}
}
};
构造函数主要初始化了一些属性,可以看到接收一个options,这个就是我们的路由配置文件传入的参数。
一般配置文件为如下
new VueRouter({
path: '/home',
component: resolve => require(['../views/Home.vue'], resolve),
meta: {
auth: false
}
}
]
})
VueRouter主要作为vue的路由插件。通过Vue.use(VueRouter)使用该插件,我们知道Vue.use会调用VueRouter的install方法,以下是install方法
function install (Vue) {
if (install.installed && _Vue === Vue) { return }
install.installed = true;
_Vue = Vue;
var isDef = function (v) { return v !== undefined; };
var registerInstance = function (vm, callVal) {
var i = vm.$options._parentVnode;
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
i(vm, callVal);
}
};
Vue.mixin({
beforeCreate: function beforeCreate () {//混入beforeCreate方法,在生成组件时会调用
if (isDef(this.$options.router)) {
this._routerRoot = this;
this._router = this.$options.router;//挂在路由对象到当前vue实例上
this._router.init(this);//调用路由的init方法
Vue.util.defineReactive(this, '_route', this._router.history.current);//挂在_route属性到vue实例上并且使它可观察
} 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 }
});
Vue.component('router-view', View);
Vue.component('router-link', Link);
var strats = Vue.config.optionMergeStrategies;
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
}
接下来我们看VueRouter的init方法:
VueRouter.prototype.init = function init (app /* Vue component instance */) {
var this$1 = this;
"development" !== 'production' && assert(
install.installed,
"not installed. Make sure to call `Vue.use(VueRouter)` " +
"before creating root instance."
);
this.apps.push(app);
// main app already initialized.
if (this.app) {
return
}
this.app = app;
var history = this.history;
if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation());
} else if (history instanceof HashHistory) {
var setupHashListener = function () {
history.setupListeners();
};
history.transitionTo(
history.getCurrentLocation(),
setupHashListener,
setupHashListener
);
}
history.listen(function (route) { //listen方法为History对象的方法,会扩展到三种类型的实现中。该方法主要是为绑定当前路由对象在路由更新时的回调函数。
this$1.apps.forEach(function (app) {
app._route = route;
});
});
};
在vue项目中我们都是通过this.$router.push(‘home’)来进行页面跳转,那么这其中到底发生了什么呢?通过阅读源码知道push方法的实现:
VueRouter.prototype.push = function push (location, onComplete, onAbort) {
this.history.push(location, onComplete, onAbort);
};
这个this.history就是我们在new VueRouter的时候生成的一个history。一般默认为HashHistory
以下是HashHistory的构造函数
var HashHistory = (function (History$$1) {
function HashHistory (router, base, fallback) {
History$$1.call(this, router, base);
// check history fallback deeplinking
if (fallback && checkFallback(this.base)) {
return
}
ensureSlash();
}
if ( History$$1 ) HashHistory.__proto__ = History$$1;
HashHistory.prototype = Object.create( History$$1 && History$$1.prototype );
HashHistory.prototype.constructor = HashHistory;
// this is delayed until the app mounts
// to avoid the hashchange listener being fired too early
HashHistory.prototype.setupListeners = function setupListeners () {
var this$1 = this;
var router = this.router;
var expectScroll = router.options.scrollBehavior;
var supportsScroll = supportsPushState && expectScroll;
if (supportsScroll) {
setupScroll();
}
window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', //通过监听hashchange来得到路由改变的通知
function () {
var current = this$1.current;
if (!ensureSlash()) {
return
}
this$1.transitionTo(getHash(), function (route) {//监听到路由改变时运行该回调函数:transitionTo是从History对象扩展来的方法,该方法调用路由更新方法
if (supportsScroll) {
handleScroll(this$1.router, route, current, true);
}
if (!supportsPushState) {
replaceHash(route.fullPath);
}
});
});
};
HashHistory.prototype.push = function push (location, onComplete, onAbort) {
var this$1 = this;
var ref = this;
var fromRoute = ref.current;
this.transitionTo(location, function (route) {
pushHash(route.fullPath);
handleScroll(this$1.router, route, fromRoute, false);
onComplete && onComplete(route);
}, onAbort);
};
HashHistory.prototype.replace = function replace (location, onComplete, onAbort) {
var this$1 = this;
var ref = this;
var fromRoute = ref.current;
this.transitionTo(location, function (route) {
replaceHash(route.fullPath);
handleScroll(this$1.router, route, fromRoute, false);
onComplete && onComplete(route);
}, onAbort);
};
HashHistory.prototype.go = function go (n) {
window.history.go(n);
};
HashHistory.prototype.ensureURL = function ensureURL (push) {
var current = this.current.fullPath;
if (getHash() !== current) {
push ? pushHash(current) : replaceHash(current);
}
};
HashHistory.prototype.getCurrentLocation = function getCurrentLocation () {
return getHash()
};
return HashHistory;
}(History));//History为全局的一个对象。主要封装三种模式共有的一些方法等。
以下为History的transitionTo方法实现
History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) {
var this$1 = this;
var route = this.router.match(location, this.current);
this.confirmTransition(route, function () {
this$1.updateRoute(route);//更新路由,路由更新时运行在init方法绑定在当前路由对象上的回调函数,具体可看updateRoute方法实现
onComplete && onComplete(route);
this$1.ensureURL();
// fire ready cbs once
if (!this$1.ready) {
this$1.ready = true;
this$1.readyCbs.forEach(function (cb) { cb(route); });
}
}, function (err) {
if (onAbort) {
onAbort(err);
}
if (err && !this$1.ready) {
this$1.ready = true;
this$1.readyErrorCbs.forEach(function (cb) { cb(err); });
}
});
};
以下是updateRoute 方法
History.prototype.updateRoute = function updateRoute (route) {
var prev = this.current;
this.current = route;
this.cb && this.cb(route);//该处的cb既为init方法通过listen绑定在history对象上的回调函数
this.router.afterHooks.forEach(function (hook) {
hook && hook(route, prev);
});
};
以上为VueRouter的主要的实现过程。下回继续分享路由变化页面如何更新