搜索框的每个字符都是一次状态变化
地图地址页的输入框在 onChanged 中直接调用搜索方法。输入一串地点名称会连续发起多次查询,网络请求数量随字符变化;如果用户正在改词,旧查询可能还在后台运行。
列表搜索可以采用更节制的做法:控制器保存一个 Timer,每次输入取消上一个计时器,再等待 400ms 后刷新列表。
void onSearchChanged(String value) {
_searchDebounce?.cancel();
_searchDebounce = Timer(const Duration(milliseconds: 400), () {
onRefresh();
});
}
控制器关闭时也取消计时器并释放输入框。这样能减少快速输入时对列表和 SmartRefresher 的连续冲击。
防抖和请求取消解决不同问题
防抖控制的是“什么时候发起下一次请求”,不能自动取消已经发出的请求。假设查询 A 先开始但响应较慢,查询 B 后开始却先返回;若 A 最后覆盖列表,页面最终显示的还是旧词结果。
更完整的实现会在每次查询时保存关键词和城市,递增 requestId,并在响应返回时只接受最新编号。也可以使用可取消的网络请求,但仍需要定义取消后的界面状态。
选一个适合业务的等待时间
400ms 是交互和请求量之间的折衷。地图地点搜索可能希望更快显示结果,弱网下也可能需要更长的等待;还可以在输入长度不足时不请求,在清空时立即恢复附近列表。
调试时记录请求发起和完成时使用的关键词,比只看列表内容更容易发现乱序问题。日志应避免输出用户输入中的地址或其他隐私信息。
两个搜索页的对照检查
- 地图地址搜索:每次输入都请求;需要防抖、请求版本和控制器释放。
- 列表搜索:使用 400ms 防抖和计时器清理;仍需验证旧网络响应是否能覆盖新结果。
- 两者都要处理空关键词、无结果、请求失败和快速切换城市。
- 搜索状态与分页状态应同步重置,避免新关键词从旧页码继续加载。
经验总结
400ms 防抖+Timer 的写法我摘出来跑过:快速输入只触发最后一次,断言通过。但这篇最有价值的是第二句——防抖只解决”何时发请求”,不解决”旧响应晚到”。查询 A 先发后到、查询 B 后发先到,A 覆盖 B 的结果,用户看到的是旧词的结果,这个 bug 在弱网下必现。修法和定位篇一样:每次查询递增 requestId,响应回来只接受最新编号。日志里记”发起时用的关键词”比只看列表更容易定位乱序,但别把用户输入的地址原文打进日志,隐私合规会找麻烦。
小结
防抖减少短时间内重复请求,requestId 或取消令牌解决响应乱序,资源释放则处理页面生命周期。三个问题相关,但各有独立职责,不能只加一个 Timer 就认为搜索竞态已经消失。
