手动防抖+分离本地过滤与远程联想是唯一兼顾响应速度、网络开销和准确性的方案:@input中cleartimeout+settimeout控制请求时机,本地数据用filter同步过滤,远程结果显式赋值并合并渲染,点击联想词需同步赋值、搜索、收键盘,历史记录须trim查重限长并try/catch兜底。

直接用 @input 绑定 + 手写防抖 + 分离本地过滤与远程联想,是唯一能兼顾响应速度、网络开销和结果准确性的做法。别指望 uni-search-bar 自带防抖,也别把历史记录、热门推荐、接口联想全塞进一个 watch 里。
为什么不能在 @input 里直接发请求
用户输入 “shenzhen”,@input 会触发 7 次(“s”“sh”“she”…),不加控制就等于 7 次无意义请求。后端可能限流返回 429,前端拿到的响应还存在竞态:最后发的请求先返回,覆盖了更精准的中间结果(比如搜 “shen”,返回“深圳湾”,但“shenz”还没回来,面板已刷成“深圳市”)。
必须手动控制触发时机:
-
data中声明searchTimer: null -
onInput(val)开头立刻clearTimeout(this.searchTimer) - 再
this.searchTimer = setTimeout(() => { this.fetchSuggestions(val) }, 300) - 输入为空或
val.length 时,直接清空 <code>suggestionList,不进setTimeout
怎么区分本地过滤和远程联想
用户搜“北京”,列表里有“北京市朝阳区”“北京烤鸭”“北京大学”——这是已有数据,同步过滤即可;而“北京天气”“北京地铁”得靠接口返回。混在一起会导致:computed 一更新就发请求,或者远程结果一到就冲掉刚输对的本地匹配项。
正确做法是拆成两个独立路径:
- 本地过滤走
computed或方法内filter(),比如localFiltered = this.allCities.filter(c => c.includes(this.searchKeyword)) - 远程联想存到
data的remoteSuggestions,由fetchSuggestions()显式赋值 - 渲染时合并:
[...localFiltered, ...remoteSuggestions],但需加标识字段(如type: 'local'/'remote')避免重复渲染
点击联想词后为什么没反应或键盘不收
常见现象:点了“上海迪士尼”,输入框内容变了,但没触发搜索;或键盘一直挂着,遮住结果列表。根本原因是只改了 v-model 绑定值,没同步处理交互链路。
必须显式补全三步:
- 赋值:
this.searchKeyword = item.keyword - 触发搜索:
this.doSearch()(别等@confirm) - 收键盘:
uni.hideKeyboard(),iOS 上尤其关键;若用了uni-search-bar,还需调this.$refs.searchBarRef.blur()
App 端历史记录和联想面板容易出错的地方
iOS 键盘收起动画未结束时,position: absolute 的联想面板会因页面高度未恢复而错位;历史记录在 H5 和小程序里存储机制不同,直接 uni.getStorageSync 不加兜底会报错。
关键细节不能漏:
- 联想面板必须用
v-if控制显隐,不是v-show;空数组 + 请求中状态要单独判断,避免 DOM 残留 - 历史记录存前先
.trim()去空格,用findIndex查重后unshift置顶,上限设为 10 条,超长就pop() - 读历史时加
try/catch,失败就初始化空数组:let history = [];try { history = JSON.parse(uni.getStorageSync('searchHistory') || '[]'); } catch (e) {}
最易被忽略的是竞态清理:每次新请求发出前,上一个未完成的请求没取消,就可能把旧结果写进当前 remoteSuggestions。真机调试时 iOS 键盘收起延迟、安卓 confirm-type="search" 失效,都得靠点击事件兜底,不能只信文档写的“自动触发”。










