History API
https://developer.mozilla.org/zh-CN/docs/Web/API/History
https://developer.mozilla.org/en-US/docs/Web/API/History_API
window.history.back();window.history.go(-1);window.history.forward();window.history.go(1);// refresh current pagewindow.history.go(0);//the same like:window.history.go();
pushState( ) replaceState( )
不会触发 popstate, 不会触发 hashchange
不会发起请求
go( ) back( ) forward( ) 会触发 popstate
浏览器上的前进后退按钮 会触发popstate
location.hash=xxx location.href=xxx 即使只改变 hash 也会触发 popstate
go( ) back( ) forward( ) pushState( ) replaceState( ) 都会影响 history 记录
hash的变化也会产生history记录
pushState()
history.pushState(state, title[, url])
state_object: 任何可被序列化的数据, 在firefox中限制序列化后大小不超过64K
title: 暂时被忽略
URL: 可选, 浏览器不会尝试载入这个URL, 但是下一次访问这个历史时会载入
在window.history中插入一条记录, 并改变地址栏展示, 但实际不会刷新页面
Note that pushState() never causes a hashchange event to be fired, even if the new URL differs from the old URL only in its hash.
The popstate event
A popstate event is dispatched to the window every time the active history entry changes.
If the history entry being activated was created by a call to pushState or affected by a call to replaceState, the popstate event’s state property contains a copy of the history entry’s state object.
See window.onpopstate for sample usage.
Calling history.pushState() or history.replaceState() won’t trigger a popstate event.
The popstate event is only triggered by performing a browser action,
such as clicking on the back button (or calling history.back() in JavaScript),
when navigating between two history entries for the same document.
Vue Router
不同路由类型:
switch (mode) {case 'history':this.history = new HTML5History(this, options.base)breakcase 'hash':this.history = new HashHistory(this, options.base, this.fallback)breakcase 'abstract':this.history = new AbstractHistory(this, options.base)breakdefault:if (process.env.NODE_ENV !== 'production') {assert(false, `invalid mode: ${mode}`)}}
Hash Mode
Hash Mode监听路由变化:
const handleRoutingEvent = () => {const current = this.currentif (!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.transitionTo() 里
编程式导航同样用了this.transitionTo()
编程式的导航
export class HashHistory extends History {...push (location: RawLocation, onComplete?: Function, onAbort?: Function) {const { current: fromRoute } = thisthis.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 } = thisthis.transitionTo(location,route => {replaceHash(route.fullPath)handleScroll(this.router, route, fromRoute, false)onComplete && onComplete(route)},onAbort)}...}
pushHash 更新历史记录
transitionTo (location: RawLocation,onComplete?: Function,onAbort?: Function) {const route = this.router.match(location, this.current)this.confirmTransition(route,() => {...},() => {...},)}
confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {...const queue: Array<?NavigationGuard> = [].concat(// in-component leave guardsextractLeaveGuards(deactivated),// global before hooksthis.router.beforeHooks,// in-component update hooksextractUpdateHooks(updated),// in-config enter guardsactivated.map(m => m.beforeEnter),// async componentsresolveAsyncComponents(activated))...const iterator = (hook: NavigationGuard, next) => { ... }runQueue(queue, iterator, () => { ... })function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) { ... }}
页面跳转后的下一步:
function pushHash (path) {if (supportsPushState) {pushState(getUrl(path))} else {window.location.hash = path}}function replaceHash (path) {if (supportsPushState) {replaceState(getUrl(path))} else {window.location.replace(getUrl(path))}}
export function pushState (url?: string, replace?: boolean) {saveScrollPosition()// try...catch the pushState call to get around Safari// DOM Exception 18 where it limits to 100 pushState callsconst history = window.historytry {if (replace) {history.replaceState({ key: _key }, '', url)} else {_key = genKey()history.pushState({ key: _key }, '', url)}} catch (e) {window.location[replace ? 'replace' : 'assign'](url)}}
History Mode
export class HTML5History extends History {...push (location: RawLocation, onComplete?: Function, onAbort?: Function) {const { current: fromRoute } = thisthis.transitionTo(location, route => {pushState(cleanPath(this.base + route.fullPath))handleScroll(this.router, route, fromRoute, false)onComplete && onComplete(route)}, onAbort)}replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {const { current: fromRoute } = thisthis.transitionTo(location, route => {replaceState(cleanPath(this.base + route.fullPath))handleScroll(this.router, route, fromRoute, false)onComplete && onComplete(route)}, onAbort)}...}
history mode 只监听了 popstate
不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问
http://oursite.com/user/id就会返回 404,这就不好看了。 所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个index.html页面,这个页面就是你 app 依赖的页面。 link
tips: pushState 不会发起请求, 只有 “直接访问” 会发起请求需要这样处理
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave守卫。 - 调用全局的
beforeEach守卫。 - 在重用的组件里调用
beforeRouteUpdate守卫 (2.2+)。 - 在路由配置里调用
beforeEnter。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter。 - 调用全局的
beforeResolve守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach钩子。 - 触发 DOM 更新。
- 用创建好的实例调用
beforeRouteEnter守卫中传给next的回调函数。
手动实现HashRouter
https://www.xiabingbao.com/post/fe/hash-history-router.html
文章用react讲的 不过都一样
