Skip to content

页面跳转规则会变化:缓存候选范围,确认时重读规则 ​

一个常见的需求:某个入口需要临时跳转到新页面。最省事的写法是判断一个固定的业务 ID,再拼一个新地址。

可是入口一多、规则有了生效日期、页面又长时间不关闭之后,这些判断就会开始互相矛盾:

  • 规则已经到期了,一直开着的旧页面还在按旧规则跳转;
  • 新加入规则的对象,刚开始一段时间没有生效;
  • 用户已经离开了页面,过一会儿却弹出一个属于上一个页面的提示;
  • 规则接口出错时,用户被卡住,什么都做不了。

本文来自一个原生微信小程序近期的导航改造,核对日期为 2026-10-23。业务对象、编号、地址和接口均已省略,只保留可以复用的控制流程。本次没有启动小程序或调用业务接口。

读完这篇你会

  • 把“哪些对象需要检查”和“此刻规则是否生效”拆成两层,分别缓存
  • 理解候选缓存带来的短暂遗漏窗口,并根据规则性质做取舍
  • 保存用户的意图而不是拼好的 URL
  • 让迟到的异步结果不会弹出属于旧操作的提示

1. 原理:登机前再看一眼屏幕 ​

在机场,你早早地在手机上看到了登机口是 A12,于是走到了那附近(缓存的候选信息)。但真正登机之前,你还会抬头再看一眼登机口的屏幕(确认时重新读取规则),因为登机口可能临时变更了。

如果只看一次手机就去登机,就可能跑错登机口;如果每走一步都去问工作人员,又太慢了。合理的做法是:平时用缓存的信息快速判断方向,在做出最终决定的那一刻再确认一次。

当前的实现把规则分成两层:

数据用途缓存策略
候选对象列表哪些对象可能需要特殊处理可以缓存,后台更新
当前生效的配置此刻是否启用、目标是哪里点击时读取,用户确认时再读取一次

这样,普通的入口不需要每次都等待规则请求;同时,有时效性的最终决定也不会被长期缓存。

text
页面显示 → 预取候选列表
    ↓
用户点击 → 是否属于候选范围
    ├─ 否 → 原流程
    └─ 是 → 读取当前配置 → 显示提示
                               ↓
                         用户确认时再读取
                               ↓
                         按新规则或原流程继续

2. 坑一:页面开着跨过了生效日期,还在按旧规则跳转 ​

现象:规则在今天零点到期了,但一个从昨天就开着的页面,用户点击后还是跳到了旧的目标。

原因:最终是否生效的配置被缓存在了页面里。页面可能跨过日期边界,提示弹窗也可能停留很久。

修复:用户确认时再读取一次配置。这解决的是“用户看到提示时的规则”和“真正执行时的规则”可能不一样的问题。

3. 坑二:新加入规则的对象,刚开始没生效 ​

现象:服务端刚把一个新对象加入了特殊跳转规则,但用户点击它时,还是走了原来的流程,过一会儿才正常。

原因:当前的候选请求用同一个进行中的 Promise 合并并发请求;缓存过期时在后台更新;第一次没有缓存时只等待有限的时间,失败了就沿用原流程。使用旧的候选列表,代价是:服务端刚加入的对象不在旧列表里,在刷新完成之前,可能仍然走原流程。

候选缓存并不是一个“毫无影响的性能优化”,它带来了一个短暂的遗漏窗口。适合的策略取决于规则的性质:

  • 只是改善体验的跳转:可以接受短暂地沿用原流程;
  • 涉及权限、支付资格或者必须执行的业务限制:应该由服务端的最终动作再次校验;
  • 规则变化要求立刻生效:需要额外的版本号、失效通知或者更严格的读取策略。

这些增强是建议,不是当前客户端代码已经提供的保障。

4. 坑三:提前拼好的 URL,规则一变就不对了 ​

现象:用户在原页面选好了一个规格,点击后弹出“即将跳转到新页面”。用户点了确认,跳过去之后,刚才选好的规格丢了,因为新地址是按旧规则提前拼好的,没有带上用户的选择。

修复:保存用户的意图,而不只是一个 URL。当前的实现记录原对象、可选的上下文和原流程的回调;用户确认后,再按照最新的规则构造目标地址,有原来的上下文就一起带过去,而不是无条件地回到一个空白的入口。

通用的意图模型可以这样表达:

ts
type NavigationIntent = {
  sourceId: string;
  selectedResourceId?: string;
  resumeOriginal: () => void;
};

意图比提前拼好的 URL 更容易重新解释:规则失效了,可以继续走原流程;规则更新了,可以换一个目标,同时保留用户已经做出的选择。

当前代码还会校验:配置是否启用、来源是否一致、目标的格式是否合法、目标是不是等于来源(避免跳回自己)。对字符串做 URL 编码只是为了正确地构造地址,不能代替这些业务条件的检查。

5. 坑四:离开页面后,弹出了旧的提示 ​

现象:用户在页面 A 点击了一个对象,规则请求还没回来,用户就离开去了页面 B。过了一会儿,页面 B 上弹出了一个“即将跳转”的提示,内容是关于 A 页面那个对象的。

原因:异步请求返回的时候,用户可能已经离开了当前的页面;确认请求返回时,弹窗也可能已经被关闭或替换了。

修复:当前的实现会检查页面是否仍然处于页面栈的顶部,并在确认阶段检查保存的上下文是否还是同一个对象。旧请求不能重新弹出属于上一轮操作的提示。

注意:“页面还存在”和“页面仍然是当前操作的目标”是两回事。只判断页面对象不为空,无法排除一个已经被隐藏到后面的旧页面。

6. 失败回退要说明自己的适用范围 ​

当前的实现中:规则接口不可用或者配置无效时,沿用原流程;跳转失败时,如果上下文仍然有效,就恢复提示并允许重试;按钮锁用来避免重复进入检查流程。

这是一种偏重可用性的选择,不适合直接套用在安全授权上。如果原流程本身已经无法完成业务,静默回退只会把问题推迟到下一步;这时应该展示明确的“暂时不可用”状态。

客户端的提示只负责导航体验,不应该成为业务规则唯一的执行点。旧版本的客户端、缓存、直接进入目标页面的情况,都需要服务端的最终行为保持一致。

排查思路 ​

  1. 跳转结果不符合规则:确认当时用的是缓存的候选列表还是刚读取的配置,确认时有没有重新读取。
  2. 新规则没生效:查看候选缓存的更新时间,判断是否处在遗漏窗口内。
  3. 弹窗出现在错误的页面:在请求返回的地方打日志,记录当时的页面栈顶和保存的上下文,确认校验是否生效。
  4. 规则接口出错:确认回退行为是否符合这个规则的性质(体验类可以回退,强制类应该提示不可用)。

建议验证 ​

场景应该核对的结果
普通对象不在候选范围内不会被额外的请求长时间阻塞
候选列表过期,且新增了对象记录为可以接受的短暂遗漏,不能当作实时保证
弹窗停留期间跨过了生效边界确认时按新的配置处理
加载过程中离开页面旧的响应不会重新打开弹窗
连续点击、关闭后重新打开提交锁和上下文分别管理
跳转失败或规则接口失败保留必要的意图,回退行为符合业务约定

检查清单 ​

  • [ ] 候选范围和当前生效配置分两层,最终决定在确认时重新读取
  • [ ] 明确了候选缓存的遗漏窗口,强制性规则由服务端再次校验
  • [ ] 保存的是用户的意图,而不是提前拼好的 URL
  • [ ] 异步结果返回时,核对页面仍是栈顶、上下文仍是同一个
  • [ ] 失败回退的方式和规则的性质相匹配

延伸阅读 ​

为复用而记录,为理解而整理。