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 page
window.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)
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 Mode
Hash Mode监听路由变化:
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.transitionTo()
里
编程式导航同样用了this.transitionTo()
编程式的导航
export class HashHistory extends History {
...
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
)
}
...
}
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 guards
extractLeaveGuards(deactivated),
// global before hooks
this.router.beforeHooks,
// in-component update hooks
extractUpdateHooks(updated),
// in-config enter guards
activated.map(m => m.beforeEnter),
// async components
resolveAsyncComponents(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 calls
const history = window.history
try {
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 } = this
this.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 } = this
this.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讲的 不过都一样