原文:
https://www.cnblogs.com/xuzhudong/p/8869699.html
前端应用的主流形式:单页应用(SPA) 大型的单页应用最显著的特点之一:采用前端路由系统,通过改变URL,在不重新请求页面的情况下,更新页面视图
更新视图但不重新请求页面是前端路由原理的核心之一,目前唉浏览器环境中这一功能的实现主要有两种方式:
- 利用URL中的hash("#")
- 利用History interface在html5中新增的方法
h5 history api:
https://developer.mozilla.org/zh-CN/docs/Web/API/History
vue-router是Vue.js框架的路由插件,下面我们从它的源码入手,了解下vue-router 是如何通过这两种方式实现前端路由的
模式参数
在vue-router中是通过mode这一参数控制路由的实现模式的:
const router = new VueRouter({
mode: 'history',
routes: [...]
})
vue-router的实际源码:
export default class VueRouter {
mode: string; // 传入的字符串参数,指示history类别
history: HashHistory | HTML5History | AbstractHistory; // 实际起作用的对象属性,必须是以上三个类的枚举
fallback: boolean; // 如浏览器不支持,'history'模式需回滚为'hash'模式
constructor (options: RouterOptions = {}) {
let mode = options.mode || 'hash' // 默认为'hash'模式
this.fallback = mode === 'history' && !supportsPushState // 通过supportsPushState判断浏览器是否支持'history'模式
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract' // 不在浏览器环境下运行需强制为'abstract'模式
}
this.mode = mode
// 根据mode确定history实际的类并实例化
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: $`)
}
}
}
init (app: any /* Vue component instance */) {
const history = this.history;
// 根据history的类别执行相应的初始化操作和监听
if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation())
} else if (history instanceof HashHistory) {
const setupHashListener = () => {
history.setupListeners()
}
history.transitionTo(
history.getCurrentLocation(),
setupHashListener,
setupHashListener
)
}
history.listen(route => {
this.apps.forEach((app) => {
app._route = route
})
})
}
// VueRouter类暴露的以下方法实际是调用具体history对象的方法
VueRouter.prototype.beforeEach = function beforeEach (fn) {
return registerHook(this.beforeHooks, fn)
};
VueRouter.prototype.beforeResolve = function beforeResolve (fn) {
return registerHook(this.resolveHooks, fn)
};
VueRouter.prototype.afterEach = function afterEach (fn) {
return registerHook(this.afterHooks, fn)
};
VueRouter.prototype.onReady = function onReady (cb, errorCb) {
this.history.onReady(cb, errorCb);
};
VueRouter.prototype.onError = function onError (errorCb) {
this.history.onError(errorCb);
};
VueRouter.prototype.push = function push (location, onComplete, onAbort) {
this.history.push(location, onComplete, onAbort);
};
VueRouter.prototype.replace = function replace (location, onComplete, onAbort) {
this.history.replace(location, onComplete, onAbort);
};
VueRouter.prototype.go = function go (n) {
this.history.go(n);
};
VueRouter.prototype.back = function back () {
this.go(-1);
};
VueRouter.prototype.forward = function forward () {
this.go(1);
};
从上面代码可以看出:
- 作为参数创图的字符串属性mode只是一个标记,用来只是实际起作用的对象属性history的的实现类,两者对应关系如下:
mode的对应--history:
HTML5History
; 'hash':HashHishtory
; abstract:AbstractHistory
; - 在初始化对应的history之前,会对mode做一些校验:若浏览器不支持HTML5History方式(通过supportPushState变量判断),则mode强制设为'abstract'
- VueRouter类中的onReady(), push()等方法只是一个代理,实际是调用的具体history对象的对应方法,在init()方法中初始化时,也是根据history对象具体的类别执行不同操作
在浏览器环境下的两种方式,分别就是在HTML5History,HashHistory两个雷中实现的。History中定义的是公用和基础的方法,简单说下: ### HashHistory hash("#")符号的本来作用是加在URL中指示网页中的位置:
> http://www.example.com/index.html#print
-
#号本身以及它后面的字符称之为hash,可通过window.location.hash属性读取。它具有以下特点:
- hash虽然出现在URL中,但不会被包括在HTTP请求中。它是用来指导浏览器动作的,对服务器端完全无用,因此,改变hash不会重新加载页面
- 可以为hash的改变添加监听事件
window.addEventListener("hashchange", funcRef, false)
- 每一次改变hash(window.location.hash),都会在浏览器的访问历史中增加一个记录
由此可见,利用hash的特点,就尅来实现前端路由“更新视图但不重新请求页面”的功能。
HashHistory.push()
源码:
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); }; // 对window的hash进行直接赋值 function pushHash (path) { if (supportsPushState) { pushState(getUrl(path)); } else { window.location.hash = path; } }
transitionTo()
方法是父类中定义用来处理路由变化中的基础逻辑的,push()
方法最主要的是对window的hash进行了直接赋值
pushHash(route.fullPath)
hash的改变会自动添加到浏览器的访问历史记录中
通过Vue.mixin()方法,全局注册一个混合,影响注册之后所有创建的每个Vue实例,该混合在beforeCreate钩子洪通过Vue.util.defineReactive()定义了响应式的_route属性。所谓响应式属性,即当_route值改变时,会自动调用Vue实例中的render()方法,更新视图。
总结一下,从**设置路由**改变到**视图更新**的流程如下:
$router.push()
-->HashHistory.push()
-->History.transitionTo()
-->History.updateRoute()
-->vm.render()