有这样一个需求:
页面上有很多标签,点击后选中,并异步调取业务接口,要求:
- 不刷新页面,同时将选中的标签值反应在 URL 的 query 参数里;
- 当跳转其他页面完成业务操作后,再次返回标签页,选中 URL 上 query 参数对应的标签值*
通常改变 URL 使用 Vue Router 的 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 信息,所以如果使用 JS 原生方法如 history.replaceState()
、history.pushState()
等,是需要把 history.state
带入的。让我们来看看在依赖 Vue Router history 模式的 history.state
里都有些什么内容:
{
"back": "/trade-manage/after-sale/detail?id=MKPIE2025081300093",
"current": "/trade-manage/after-sale?tag=6",
"forward": "/trade-manage/after-sale/detail?id=MKPIE2025081300093",
"replaced": true,
"position": 51,
"scroll": {
"left": 0,
"top": 0
}
}
从语义上就可以知道,它包含了前进、后退指向地址,甚至是滚动条位置等信息。
通过适当的调整其中的参数,Vue Router 会依照调整后的内容执行相关操作。
如此,遵照官方的要求,我们把 state 置入 history.replaceState()
的第一个参数里,在此之前还需要改变下 history.state.current
的值,它表示当前地址信息,关系到 Vue Router 对路由导航的地址定位,最终得到了以下方法:
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)
}
由此,问题解决,需求得到了满足。