有这样一个需求:
页面上有很多标签,点击后选中,并异步调取业务接口,要求:
- 不刷新页面,同时将选中的标签值反应在 URL 的 query 参数里;
- 当跳转其他页面完成业务操作后,再次返回标签页,选中 URL 上 query 参数对应的标签值*
通常改变 URL 使用 Vue Router(v4.x) 的 router.push()、router.replace() 方法:
router.replace({
query: {
tag: selectedTag.value
}
})
但是他们都会立即跳转页面,可能打断用户的操作。JS 原生的 history.replaceState 用于操作 history 记录,它不会立即刷新页面,用它来替换 URL 的 query 再合适不过了:
export function updateQuery(query:{
[key:string]: string|undefined
}={}){
const url=new URL(window.location.href)
// 根据方法参数重置query
Object.keys(query).forEach(key=>{
url.searchParams.set(key,query[key]??'')
})
window.history.replaceState(null, '', url)
}
在标签上绑定click事件并调用:
updateQuery({
tag: selectedTag.value,
})
当点击标签时,就会发现浏览器地址栏的 URL 如预期的发生着变化,并且页面没有重新加载。
但是,很快就发现:当切换到下一个页面,再返回(浏览器的返回上一页操作),此时之前给定的参数并未出现再URL的query里,是的,query参数丢了!
浏览器调试区出现了警告,告诉我们应该阅读下 Vue Router 的官方说明,显然是哪里漏了关键内容。在文档里,找到了以下描述:
“Vue Router 将信息保存在 history.state 上。”
“我们使用历史状态(history.state)来保存导航信息,如滚动位置,以前的地址等。”
原来,Vue Router 在 history 模式下依赖 state 信息,所以若想在 Vue Router 驱动的路由框架内使用 history.replaceState()、history.pushState() 等 JS 原生方法,是需要把 history.state 带入的。让我们先来看看此时 history.state 里都有些什么内容:
{
"back": "/xxxx/detail?id=546546465",
"current": "/trade-manage/after-sale?tag=6",
"forward": "/xxxx/detail?id=546546465",
"replaced": true,
"position": 51,
"scroll": {
"left": 0,
"top": 0
}
}
从语义上就可以知道,它包含了前进、后退操作指向的地址,甚至是滚动条位置等信息。通过适当的调整这些参数,Vue Router 会依照调整后的内容执行相关操作。
如此,在执行 history.replaceState() 前改变下 history.state.current (它表示当前地址信息)的值,再把当前的 history.state 传入 history.replaceState() 的第一个参数来手动更新下当前路由的 history.state。这样在跳转到下一条路由后 Vue Router 会依据自身逻辑主动把 history.state.current 的值填充到下一条路由的 history.state.back 上,从而决定在下一条路由下执行“回退”操作时指向的地址。经过梳理最终得到了以下方法:
export function updateQuery(query:{
[key:string]: string|undefined
}={}){
const url=new URL(window.location.href)
const state=window.history.state
// 根据方法参数重置query
Object.keys(query).forEach(key=>{
url.searchParams.set(key,query[key]??'')
})
// 变更current,在跳转前明确当前页面的url,包括query
state.current=url.href.replace(new RegExp(`^${url.origin}`),'')
// 将state置入
window.history.replaceState(state, '', url)
}
简单理解就是:每次变更 URL 的 query 时,同步更新下 history.state.current。
由此,问题解决,需求得到了满足。