React 定位副作用引发重复请求 (Geolocation Side Effects Causing Duplicate Requests)
触发条件
- 页面数据查询依赖
userPosition对象。 - 应用根组件、页面组件和定位展示组件各自发起浏览器定位。
- 定位展示组件随 Tab 条件渲染反复挂载,并在挂载时绕过已有定位缓存。
- 每次定位完成都创建新的位置对象;GPS 轻微抖动也会被广播为位置变化。
- 数据加载把“校区静态数据”和“依赖位置的周边数据”绑在同一个全页 loading 生命周期中。
症状
- 页面数据已经显示,随后突然重新出现整页 loading。
- 同一校区的静态接口在一次进入过程中请求两到三次。
- 切换 Tab 后是否复现不稳定:GPS 返回值完全相同时可能不触发,坐标轻微变化时触发。
- 逆地理缓存明明命中,POI、精选和周边列表仍重复请求。
容易误判的方向
- 只检查逆地理接口的 TTL。逆地理结果缓存不等于位置采集缓存,也不等于业务列表缓存。
- 只优化后端接口延迟。服务端即使只有毫秒级,公网多次传输较大的 JSON 加上全页 loading 仍然明显。
- 只用值相等判断抑制广播。多个调用方仍可能直接
setUserPosition(newObject),触发依赖对象引用的 Effect。 - 把问题归因于 React Strict Mode。生产环境也会因真实的多调用点、组件重挂载和定位回调时序复现。
根因
这是三个独立生命周期被错误耦合后的结果:
- 位置采集:多个组件并发调用
getCurrentPosition(),但没有共享进行中的 Promise。 - 位置状态传播:每个完成回调都写状态或广播事件,新对象引用和 GPS 抖动均可触发更新。
- 业务数据加载:
userPosition一变化,就重新加载所有校区数据,并把已有内容替换成全页 loading。
最终解决原则
- 为完整定位链路建立单飞 (Singleflight):学校桥、原生桥和浏览器 Geolocation 共用一个进行中的 Promise。
- 自动定位统一走
ensureLocation():先读 TTL 缓存,只有过期或用户明确点击“重新定位”时才强制采集。 - 位置状态使用稳定的标量查询键,例如坐标网格加坐标系,而不是直接依赖对象引用。
- 对位置变化设置有意义的距离阈值;只有跨出网格或移动超过阈值才刷新周边结果。
- 拆分查询:校区、校内 POI、精选按校区缓存;周边列表才依赖位置键;逆地理使用自己的位置键。
- 后台刷新保留旧数据,只显示局部刷新状态;首次无数据才使用整页 loading。
- 为请求增加取消或序列号保护,避免旧请求晚到覆盖新请求或提前关闭 loading。
验证方法
- 清空定位缓存,模拟延迟 1–2 秒的 Geolocation,记录 UI 状态和请求时间线。
- 执行“首页 → 周边 → 首页”,分别测试相同坐标和轻微抖动坐标。
- 断言同一校区的静态接口首次只请求一次,切 Tab 不再请求。
- 断言定位到达后只刷新依赖位置的查询,已有内容不被整页 loading 替换。
- 检查响应头和浏览器网络面板,区分应用内缓存、HTTP 缓存和 CDN 缓存。
识别口诀
“数据先出现、定位后转圈”时,优先检查:谁在定位、谁在广播、谁把位置变化绑定成了全量刷新。
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Frontend