在前端spa应用中,前端路由占据了举足轻重的地位。改变了浏览器的url地址,不请求服务器资源,自动刷新渲染页面视图。
那么在前端路由中是如何实现的呢?通过vueRouter的实现方式做一个简要的了解。我们都知道在vueRouter中,浏览器下可以通过mode字段来控制路由的模式,而vueRouter将路由模式分为了两个类型:
hash模式
history模式
首先可以了解一下是如何通过mode字段来实现路由的模式分配的,VueRouter的默认路由模式是hash模式,并且在浏览器不支持history模式(即不支持history.pushState)时会自动降级为hash模式:
export default class VueRouter {
// 只看主要逻辑,目前使用的flow类型检测
constructor (options: RouterOptions = {}) {
this.app = null
this.apps = []
this.options = options
// mode默认采用hash模式
let mode = options.mode || 'hash'
// 回退标志位,是否支持pushState
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = '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: ${mode}`)
}
}
}
}
1. hash模式
现在我们看看hash模式下如何实现的改变url,不请求服务器数据而自动刷新视图。
‘#’ hash符本来在浏览器中的作用是用来表示页面位置的,如我们常用的锚点跳转。
<a href="#box2">锚点2</a>
1
而在浏览器中改变hash值得时候浏览器会自动将此次的url变化推入导航堆栈中,但是并不会向服务器发送请求。那么此时我们只需要知道hash值变化了,从而触发我们的应用更新对应视图就实现了路由的基础功能了。
监听浏览器中的hash值变化(url手动输入改变hash值)可以通过hashchange事件来做到:
window.addEventListener('hashchange', fn)
1
那么当在路由中push一次url变化,vueRouter是如何实现通知vue来更新视图的呢?现在看到router中的push方法是怎样定义的:
// index.js
export default class VueRouter {
// router初始化时写入listen的cb以便更新最新的route数据
init(app: any /* Vue component instance */) {
this.apps.push(app)
const history = this.history
history.listen(route => {
// route数据修改时,对应更新vm实例上的_route数据
this.apps.forEach((app) => {
app._route = route
})
})
}
// router上的push方法代理的是history对象上的push方法
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
// 在调用push方法时如未传递onComplete,onAbort回调函数,且支持Promise则返回一个promise对象
// $flow-disable-line
if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
this.history.push(location, resolve, reject)
})
} else {
this.history.push(location, onComplete, onAbort)
}
}
}
// hash.js
export class HashHistory extends History {
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
// 继承自History类的方法
this.transitionTo(
location,
route => {
// 修改浏览器上的url值
pushHash(route.fullPath)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
},
onAbort
)
}
}
function pushHash (path) {
if (supportsPushState) {
pushState(getUrl(path))
} else {
window.location.hash = path
}
}
// base.js
export class History {
transitionTo (
location: RawLocation,
onComplete?: Function,
onAbort?: Function
) {
const route = this.router.match(location, this.current)
// 校验此次跳转是否合法的方法
this.confirmTransition(
route,
() => {
// 更新current数据,并调用listen中写入的cb函数
this.updateRoute(route)
// 调用传入的完成函数,此处为hash.js中push时传入的第二个参数
onComplete && onComplete(route)
this.ensureURL()
// fire ready cbs once
if (!this.ready) {
this.ready = true
this.readyCbs.forEach(cb => {
cb(route)
})
}
},
err => {
// 错误处理回调
}
)
}
}
那么现在可以来看router在hash模式下一次push会发生的过程了:
init -> router.push() -> hashHistory.push() -> History.transitionTo() -> History.updateRoute() -> History.cb()
// listen时传入的cb,遍历this.apps中每一个vm实例更新_route数据,触发vm的render函数更新视图
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。