Vue3连续快速调用this.$router.replace引发竞态条件问题
问题描述
Vue3应用中实现筛选功能时,通过路由更新查询参数的核心实现如下:
this.$router.replace({ path: window.location.pathname, query }).catch(err => { console.log(err); });
查询参数query由多个计算属性的值生成。执行筛选重置操作时,所有关联筛选值会被逐个设为默认值,每个筛选属性变更都会单独触发一次router.replace调用。短时间内多次连续的replace调用存在竞态问题,最终生成的URL查询参数不符合预期,控制台日志中可观察到多次调用传入的query值不一致:
解决方法
1. 防抖合并多次路由更新(推荐优先使用)
给路由更新逻辑加防抖,将短时间内连续触发的多次更新合并为一次执行,等所有筛选状态更新完成后,基于最终生成的query值只执行一次路由替换,从根源上避免重复调用。
参考实现:
import { debounce } from 'lodash-es' // 防抖时长根据交互体感调整,100-300ms基本无感知 const syncQueryToRoute = debounce((query) => { // 优先取路由实例的当前path,避免window.location取值和路由内部base配置不一致 this.$router.replace({ path: this.$route.path, query }).catch(err => console.log(err)) }, 150) // 统一监听所有筛选字段变化,触发防抖更新 watch( () => [filter1.value, filter2.value, filter3.value], () => { const finalQuery = { // 基于所有筛选字段的最新值组装query filter1: filter1.value, filter2: filter2.value, filter3: filter3.value } syncQueryToRoute(finalQuery) } )
2. 批量更新筛选状态,避免中间态触发
重置筛选时不要逐个修改单个响应式状态,采用批量赋值的方式一次性更新所有筛选值,Vue会将批量状态修改合并为一次响应式触发,只会执行一次路由更新。
- 选项式API可在
$nextTick前完成所有状态赋值 - 组合式API建议用单个响应式对象统一管理所有筛选状态,重置时直接整体赋值:
// 组合式API示例 const filterForm = reactive({ category: '', status: '', timeRange: [] }) const resetFilter = () => { // 整体赋值只会触发一次监听回调 Object.assign(filterForm, { category: 'all', status: 0, timeRange: [] }) }
3. 增加更新版本标记,丢弃过期请求
如果业务场景无法合并更新,可通过递增版本标记的方式,校验每次路由更新的时效性,非最新版本触发的更新结果直接丢弃:
let queryUpdateVersion = 0 const syncQueryToRoute = (query) => { const currentVersion = ++queryUpdateVersion this.$router.replace({ path: this.$route.path, query }).finally(() => { // 版本号不匹配说明已有更新的请求入队,本次结果作废 if (currentVersion !== queryUpdateVersion) return }) }
内容的提问来源于stack exchange,提问作者Andreas
相关产品推荐
相关产品推荐

