返回博客

小程序钻取筛选叠加——「只覆盖传入字段」破坏钻取语义

在一个 C 端小程序里,列表页支持多条件筛选(等级、标签、时间范围等),筛选结果以 chip 形式展示在顶部。某次迭代后,数据概览页新增了"查看详情"按钮,点击后通过 navigateTo 钻取到列表页并携带分群参数。上线没多久,用户反映"看到的数据不对"——列表里出现了莫名其妙的交叉条件。

现象

复现路径只有三步:

  1. 在列表页手动筛选:等级=金卡 + 标签=活跃用户,看到符合条件的结果;
  2. 切换到数据概览页,点击「近 7 日新增」右侧的"查看详情";
  3. 跳转回列表页——期望:只展示近 7 日新增这一个分群;实际:列表标题区域同时亮起"金卡""活跃用户""近 7 日新增"三个 chip,数据是三重过滤的交集,完全不是分析页想传达的意图。

UI 层面更直观:chip 区域新旧筛选同时高亮,用户无法分辨哪个是自己设的、哪个是系统带来的。

根因

问题出在列表页的 onLoad 里:

// ❌ 反模式:只覆盖传入字段
onLoad(query) {
  if (query.groupId)   this.setData({ selectedGroup: query.groupId });
  if (query.levelCode) this.setData({ selectedLevel: query.levelCode });
  if (query.keyword)   this.setData({ keyword: query.keyword });
  // 未传入的字段保持 data 里的旧值
}

小程序 navigateTo 到一个新实例时,Page 对象确实是新建的,但 data 的初始值来自 Page({ data: { ... } }) 定义。若用户上一次访问后通过缓存或其他手段恢复了状态(比如 onLoad 外的初始化逻辑、Storage 读取),这些值就会残留。更常见的情况是:列表页和分析页在同一个页面栈里,navigateBacknavigateTo 时对象重建,但 onLoad 只写了传入字段,其余字段维持默认值——如果默认值刚好是用户上次操作后留下的副本(比如写进了 globalData 或 Storage),就会叠加。

坑:钻取语义 ≠ 合并筛选

"只 setData 传入字段"看起来保守、安全,实际上悄悄把钻取变成了叠加

钻取的业务语义是「新来源接管展示」——用户点的是"查看这些人",不是"在当前筛选基础上再加一个条件"。任何未被显式清零的 chip 字段都可能污染结果,而且这类 bug 极难复现(需要特定的操作序列),线上出了才发现。

正解

核心原则:检测到钻取意图 → 先全量重置筛选字段 → 再用钻取参数覆盖

推荐在跳转 URL 里加显式 source 标记,避免"凡是带 query 就算钻取"的误判:

// 分析页跳转时带 source 标记
wx.navigateTo({
  url: `/pages/list/index?source=analytics&groupId=${groupId}`,
});

列表页 onLoad 改写:

// ✅ 正解:先清空再覆盖
onLoad(query) {
  const isDrillDown = query && query.source === 'analytics';
 
  if (isDrillDown) {
    // Step 1: 显式列出所有 chip 对应的 data 字段,全部清零
    const resetFilter = {
      keyword: '',
      selectedLevel: '',
      selectedGroup: '',
      selectedTag: '',
      selectedChannel: '',
      startDate: '',
      endDate: '',
      sort: 'default',
    };
 
    // Step 2: 用钻取参数覆盖对应字段
    if (query.groupId)   resetFilter.selectedGroup = query.groupId;
    if (query.levelCode) resetFilter.selectedLevel = query.levelCode;
    if (query.tagCode)   resetFilter.selectedTag   = query.tagCode;
    if (query.keyword)   resetFilter.keyword        = query.keyword;
 
    // Step 3: 一次 setData 批量写入,减少 diff 开销
    this.setData(resetFilter);
  }
  // 非钻取进入(普通入口、冷启动)不做重置,保持默认 data
}

WXML 层也要配合,避免字段值为空时 chip 仍然渲染:

<view wx:if="{{selectedLevel}}" class="chip">
  {{levelLabel}} <icon bindtap="clearLevel">×</icon>
</view>
正解:检测钻取意图后先全量重置再覆盖
  • source 标记让意图明确,未来新增入口(推送消息链接、分享卡片等)可以按 source 值选择不同的重置策略;
  • resetFilter 对象必须枚举所有 chip 字段——漏一个就会漏掉一个隐患;
  • 一次 setData 批量写入,比多次调用性能更好,也避免中间态渲染;
  • onLoad 只负责状态准备,数据拉取保持在 onShow 触发,两者职责分离。

反模式速查

| 写法 | 问题 | |------|------| | this.setData(query) 整体铺 | query 可能含非筛选字段(scene、from),会污染 data | | Object.assign(this.data, query) | 不触发视图更新,还会污染原型链 | | 用 onShow 做重置 | onShow 在 navigateBack 返回时也会触发,会误清用户在当前页的筛选 | | 只覆盖传入字段 | 本文的主角——看似安全,实为叠加 |

一句话外卖

钻取跳转的语义是"接管"不是"叠加"——onLoad 收到钻取意图时,先显式清零所有筛选字段,再用参数覆盖目标字段。