返回博客

微信小程序搜索框删空不刷新:「负向事件」被遗漏的坑

做列表页搜索时,开发者通常只关心「用户输入了什么」,却忽略了另一面——「用户删掉了什么」。这个坑在小程序里几乎是必踩的,复现路径极短,但根因定位却容易绕弯。

现象

复现步骤:

  1. 打开带搜索框的列表页,输入关键词 → bind:input 触发 → 发请求 → 返回过滤结果
  2. 长按全选删除 / 连按 backspace 清空输入框 → 输入框为空
  3. 列表仍然显示旧的过滤结果,没有重拉全量数据
  4. 点击 tab 切换排序 → 发了新请求,但 keyword 参数时序错乱,或携带残留空白字符 → 仍然只显示过滤子集

用户感知:「我明明删掉了搜索词,为什么还在过滤?」

根因

只处理了正向事件

典型的反模式写法:

// ❌ 反模式
onSearchInput(e) {
  this.setData({ keyword: e.detail.value }); // 只改 data,不发请求
},
 
onSearchConfirm() {
  this.getList(true); // 只在用户按「搜索」确认键时才请求
},

开发者的心理模型是「输入过程中别频繁请求,只在用户确认时发一次」——这是对的,但漏掉了一个负向事件

"关键词" → "",在用户心智里等于「我取消这个筛选条件了」,应该等价于点 X 清空。

bind:input 只把 data.keyword 改成了空字符串,列表里的过滤结果没被清——UI 状态和数据状态永久错位

坑:setData 不等于重新请求

bind:input 只负责把输入值同步到 data,它不会自动触发 getList。当 keyword 从有值变成空字符串时,列表停在了上一次请求的结果里,而用户看到的空搜索框根本不知道还有「幽灵过滤」在生效。

为什么切 tab 也没救

changeSort / changeTab 里调用 getList,逻辑上看应该能刷——但如果用户没删干净(末尾留了一个空格),keyword 是 " " 而非 "",请求参数就会带上这个「伪非空」值,后端仍然执行 LIKE '% %' 导致莫名过滤。更糟糕的是,setData 是异步的,如果 changeSort 先读了 this.data.keyword,可能拿到前一帧的老值,产生时序竞争。

正解

onInput 里检测「删空」并立即触发

// ✅ 修复方案
onSearchInput(e) {
  const newVal = e.detail.value;
  const prevVal = this.data.keyword;
  this.setData({ keyword: newVal });
 
  // 负向事件:从有值变为空(含空白字符) → 等价于点 X 清空
  if (prevVal && !newVal.trim()) {
    this.getList(true);
  }
},
 
onSearchConfirm() {
  this.getList(true);
},

使用 van-search 时,同步绑定 bind:clear

Vant Weapp 的 van-search 右侧 X 图标不走 bind:change,走的是独立的 bind:clear 事件:

<van-search
  value="{{ keyword }}"
  placeholder="搜索"
  bind:change="onSearchInput"
  bind:search="onSearchConfirm"
  bind:clear="onSearchClear"
/>
onSearchClear() {
  this.setData({ keyword: '' });
  this.getList(true);
},

双保险:用户点 X 走 bind:clear,backspace 删空走 bind:change + 空值检测,两条路都覆盖。

正解:空值检测 + bind:clear 双保险

onInput 里判断 prevVal && !newVal.trim(),检测到「从非空变空」时立即 getList(true)。同时绑定 bind:clear 处理 X 图标点击。请求参数组装时也要对 keyword 做 trim(),防止空格伪非空绕过过滤。

请求侧也要 trim

getList(reset) {
  const keyword = (this.data.keyword || '').trim(); // 必须 trim
  wx.request({ data: { keyword, page: this.data.page, ... } });
}

可选:与防抖结合

如果要做「边输边搜」,防抖和清空检测不冲突——清空走立即触发,其余走 debounce:

onSearchInput(e) {
  const newVal = e.detail.value;
  const prevVal = this.data.keyword;
  this.setData({ keyword: newVal });
 
  if (prevVal && !newVal.trim()) {
    this.getList(true); // 清空立即触发,不走防抖
    return;
  }
 
  clearTimeout(this._searchTimer);
  this._searchTimer = setTimeout(() => this.getList(true), 400);
},

一句话外卖

搜索框的「清空」是一个独立的负向事件,必须和「确认搜索」享有同等的请求触发权重——凡是能改变过滤条件的用户行为,都要让列表状态即时收敛。