Loading状态怎么避免闪烁或重复请求?
我在用 Vue 做一个搜索功能,每次输入关键词就发请求,但发现如果用户打字快,会触发多次请求,而且 Loading 状态一闪一闪的特别难受。我试过加防抖,但有时候还是会出现上一次请求还没结束,新的请求又来了,导致界面状态错乱。
下面是我现在的代码,loading 是在请求开始设为 true,结束设为 false,但这样处理好像不够稳:
<template>
<div>
<input v-model="keyword" @input="debouncedSearch" />
<div v-if="loading">加载中...</div>
<ul><li v-for="item in results" :key="item.id">{{ item.name }}</li></ul>
</div>
</template>
<script>
export default {
data() {
return { keyword: '', loading: false, results: [] }
},
methods: {
async search() {
this.loading = true
const res = await api.search(this.keyword)
this.results = res.data
this.loading = false
}
},
created() {
this.debouncedSearch = _.debounce(this.search, 300)
}
}
</script>
按照规范,得加一层“请求上下文”或“版本号”机制,让每个请求带上自己的标识,只处理最新那次请求的结果。最简单的做法是用一个
requestId或currentQuery字段来标记当前有效的请求。比如这样改:
data() {
return {
keyword: '',
loading: false,
results: [],
currentQuery: '' // 标记当前有效的搜索关键词
}
},
methods: {
async search() {
// 先记录当前请求对应的关键词
const query = this.keyword
this.currentQuery = query
this.loading = true
try {
const res = await api.search(query)
// 关键:只有当当前结果对应的关键词还是这次请求的,才更新
if (this.currentQuery !== query) return
this.results = res.data
} finally {
// 只在最后一次有效请求结束后才关 loading
if (this.currentQuery === query) {
this.loading = false
}
}
}
}
防抖还是得保留(比如
debounce(300)),但防抖只是减少不必要的请求,真正防止闪烁的是这个currentQuery校验——它确保只有“最新一次输入”对应的请求结果会被采纳,旧请求就算晚回来了也直接被丢弃。另外注意:
finally里不能无脑设loading = false,必须判断是不是“当前有效请求”结束,否则上一个慢请求结束时会错误地关掉 loading。我之前踩过这坑,以为加个防抖就万事大吉,结果测试时一连打“abc”“abcd”“abcde”,前两个请求慢悠悠回来,界面先显示“abc”的结果,然后突然切回“abcde”的,Loading 也闪得像霓虹灯……后来加了这个判断才消停。
先说结论:别用全局
loading,要给每次请求打个「请求序号」或者「token」,只响应最新那次请求的结果,老请求回来就直接丢掉。拿去改改这个版本:
另外补充一点:防抖时间别太短,300ms 对搜索来说可能还是有点快,建议 500ms 起步,除非你后端真能扛住高频请求。
你要是图省事,也可以用
axios的取消令牌,或者直接用AbortController把旧请求 cancel 掉,不过上面这种「请求 ID」方式简单粗暴好理解,改起来也快,拿去试试。