浅析 vue-router 的三种模式
面试官:请说一下 vue-router 的 2 种模式 …
我:vue-router 不是有 3 种模式吗???
一. 前言
vue-router 到底有几种模式?
依据 vue-router 官网,咱们能够明确看到 vue-router 的 mode 值有 3 种
hash
history
abstract
其中,hash 和 history 是 SPA 单页应用程序的根底。
先说论断:spa 利用路由有 2 种模式,hash 和 history,vue 路由有 3 种模式,比 spa 多了一个 abstract。
二. 源码剖析
在 vue-router 中通过 mode 这个参数批改路由的模式:
const router = new VueRouter({
mode: 'history',
routes: [...]
})
具体怎么实现的呢,首先咱们下载 vue-router 的源码
抽离进去对 mode 的解决
class vueRouter {constructor(options) {
let 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}`)
}
}
}
}
能够看到默认应用的是 hash 模式,当设置为 history 时,如果不反对 history 办法,也会强制应用 hash 模式。
当不在浏览器环境,比方 node 中时,间接强制应用 abstract 模式。
hash 模式
浏览这部分源码前,咱们先来理解下 hash 的根底:
依据 MDN 上的介绍,Location 接口的 hash 属性返回一个 USVString,其中会蕴含 URL 标识中的 ‘#’ 和 前面 URL 片段标识符,’#’ 和前面 URL 片段标识符被称为 hash。
它有这样一些特点:
- 在第一个 #前面呈现的任何字符,都会被浏览器解读为地位标识符。这意味着,这些字符都不会被发送到服务器端。
- 单单扭转 #后的局部,浏览器只会滚动到相应地位,不会从新加载网页。
- 每一次扭转 #后的局部,都会在浏览器的拜访历史中减少一个记录,应用 ” 后退 ” 按钮,就能够回到上一个地位。
- 可通过 window.location.hash 属性读取 hash 值,并且 window.location.hash 这个属性可读可写。
- 应用 window.addEventListener(“hashchange”, fun) 能够监听 hash 的变动
理解了这些基本知识后,咱们持续来看 vue-router 源码对 /src/history/hash.js 的解决
const handleRoutingEvent = () => {
const current = this.current
if (!ensureSlash()) {return}
this.transitionTo(getHash(), route => {if (supportsScroll) {handleScroll(this.router, route, current, true)
}
if (!supportsPushState) {replaceHash(route.fullPath)
}
})
}
const eventType = supportsPushState ? 'popstate' : 'hashchange'
window.addEventListener(
eventType,
handleRoutingEvent
)
this.listeners.push(() => {window.removeEventListener(eventType, handleRoutingEvent)
})
首先也是应用 window.addEventListener(“hashchange”, fun) 监听路由的变动, 而后应用 transitionTo 办法更新视图
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {const { current: fromRoute} = this
this.transitionTo(
location,
route => {pushHash(route.fullPath)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
},
onAbort
)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {const { current: fromRoute} = this
this.transitionTo(
location,
route => {replaceHash(route.fullPath)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
},
onAbort
)
}
vue-router 的 2 个次要 API push 和 replace 也是简略解决了下 hash , 而后调用 transitionTo 办法更新视图
history 模式
老规矩,先来理解下 HTML5History 的的基本知识:
依据 MDN 上的介绍,History 接口容许操作浏览器的已经在标签页或者框架里拜访的会话历史记录。
应用 back(), forward() 和 go() 办法来实现在用户历史记录中向后和向前的跳转。
HTML5 引入了 history.pushState() 和 history.replaceState() 办法,它们别离能够增加和批改历史记录条目。
略微理解下 history.pushState():
window.onpopstate = function(e) {alert(2);
}
let stateObj = {foo: "bar",};
history.pushState(stateObj, "page 2", "bar.html");
这将使浏览器地址栏显示为 http://mozilla.org/bar.html,但并不会导致浏览器加载 bar.html,甚至不会查看 bar.html 是否存在。
也就是说,尽管浏览器 URL 扭转了,但不会立刻从新向服务端发送申请,这也是 spa 利用 更新视图但不 从新申请页面的根底。
接着咱们持续看 vue-router 源码对 /src/history/html5.js 的解决:
const handleRoutingEvent = () => {
const current = this.current
// Avoiding first `popstate` event dispatched in some browsers but first
// history route not updated since async guard at the same time.
const location = getLocation(this.base)
if (this.current === START && location === this._startLocation) {return}
this.transitionTo(location, route => {if (supportsScroll) {handleScroll(router, route, current, true)
}
})
}
window.addEventListener('popstate', handleRoutingEvent)
this.listeners.push(() => {window.removeEventListener('popstate', handleRoutingEvent)
})
解决逻辑和 hash 类似,应用 window.addEventListener(“popstate”, fun) 监听路由的变动, 而后应用 transitionTo 办法更新视图。
push 和 replace 等办法就不再具体介绍。
abstract 模式
最初咱们间接来看一下对 /src/history/abstract.js 的解决:
constructor (router: Router, base: ?string) {super(router, base)
this.stack = []
this.index = -1
}
首先定义了 2 个变量,stack 来记录调用的记录,index 记录以后的指针地位
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.transitionTo(
location,
route => {this.stack = this.stack.slice(0, this.index + 1).concat(route)
this.index++
onComplete && onComplete(route)
},
onAbort
)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.transitionTo(
location,
route => {this.stack = this.stack.slice(0, this.index).concat(route)
onComplete && onComplete(route)
},
onAbort
)
}
push 和 replac 办法 也是通过 stack 和 index 2 个变量,模拟出浏览器的历史调用记录。
三. 总结
终于到了最初的总结阶段了:
- hash 和 history 的应用形式差不多,hash 中路由带 #,然而应用简略,不须要服务端配合,站在技术角度讲,这个是配置最简略的模式,自己感觉这也是 hash 被设为默认模式的起因
- history 模式须要服务端配合解决 404 的状况,然而路由中不带 #,比 hash 好看一点。
- abstract 模式没有应用浏览器 api,能够放到 node 环境或者桌面利用中,自己感觉是对 spa 利用 的兜底和能力扩大。
如果有谬误或者不谨严的中央,请务必给予斧正,非常感激。如果喜爱或者有所启发,欢送 star github,对作者也是一种激励。