HarmonyOS API 26 Navigation 菜单项刷新态:stale 数据、重试与一致性

Navigation 菜单项常常来自接口、权限和灰度配置。请求刷新期间,上一份数据可能仍然可用;如果把它立即清空,用户会看到入口闪烁,甚至失去返回路径。本文围绕 stale 数据、刷新重试和版本一致性,建立应用侧的状态模型。

一、为什么要区分fresh与stale

fresh 表示仍在有效期内,stale 表示可以暂时展示但需要刷新,missing 表示没有任何可用快照。

二、API26的边界

示例只描述菜单策略,不声称系统解析或编译结果。正式接入仍应以目标 SDK 文档为准。

三、MenuSnapshot模型

快照包含稳定 key、更新时间、版本和业务 enabled。快照是展示依据,不等同于服务端权威数据。

四、freshnessOf纯函数

纯函数根据 now、updatedAt 和 maxAge 得出新鲜度,便于覆盖边界时间。

ts 复制代码
const freshness = freshnessOf(snapshot, context.now, context.maxAge)

五、RefreshContext请求上下文

requestId 用于淘汰旧响应,state 表示刷新阶段,hasError 表示本轮是否失败。

六、stale仍可用

stale 数据可以保留原入口和路由,但需要显示轻量刷新提示,不应伪装成最新结果。

七、missing的首屏策略

没有快照时显示骨架或禁用入口;刷新完成前不应让用户进入空详情页。

八、refreshing的两种视觉

fresh 刷新可以保留原文案,missing 刷新使用骨架,stale 刷新使用小型进度提示。

ts 复制代码
if (decision.refreshing) showProgress()

九、失败后的重试入口

stale 失败可保留旧数据并提供重试;missing 失败应说明暂无可用数据,避免点击后无反馈。

十、版本一致性

同一个 key 的版本变化意味着内容发生变更。提交导航前可再次比较版本,防止旧快照覆盖新状态。

ts 复制代码
if (!sameVersion(before, current)) refreshMenu()

十一、requestId与竞态

刷新请求必须绑定 requestId,只有最新请求可提交结果。旧请求即使晚到,也只能被丢弃。

十二、保留首层稳定性

stale 不应改变 primary 与 More 的排序。placement 只有在功能权限或设备形态变化时才重新计算。

十三、More入口一致性

More 中的 stale 项也要显示更新提示,不能因为入口被折叠就丢失状态语义。

十四、点击前快照校验

点击时检查最新版本和 enabled,防止用户在长时间停留后使用过期入口。

ts 复制代码
if (snapshot.version !== latest.version) return

十五、重试退避

连续失败时采用递增退避,避免用户快速点击造成请求风暴。退避只影响请求,不改变 stale 数据展示。

十六、错误文案

文案应该告诉用户内容是否还能使用、何时可以重试以及是否需要联网,不直接展示堆栈。

十七、设备切换

横屏、平板或小窗口切换后重算布局,但不要无条件清空快照。相同查询条件可继续使用原结果。

十八、无障碍播报

辅助文本应读出"内容可能已过期,正在更新"或"暂无数据,可重试",而不是只读一个禁用按钮。

十九、深色模式

刷新提示、时间徽标和重试按钮都要保持足够对比度,避免深色背景下 stale 标记不可见。

二十、缓存有效期

maxAge 应是明确的产品参数。不同动作可以有不同有效期,但必须在策略层集中管理。

二十一、日志

日志记录 key、version、freshness、requestId 和策略版本,不记录账号、令牌和完整路由参数。

二十二、测试矩阵

覆盖 fresh、stale、missing、刷新成功、刷新失败、旧响应晚到、版本变化、设备切换和 More 回退。

ts 复制代码
assert.equal(decision.staleNotice, true)

二十三、与Navigation的分工

Navigation 负责承载页面和菜单,服务层负责请求与快照,策略函数只输出可呈现结果。

二十四、小结

stale 数据不是错误,也不是最新数据。把它单独建模,才能在刷新、重试与一致性之间取得平衡。

二十五、最终边界声明

本文未执行 HarmonyOS 构建、系统 EasyGo 或 Navigation 解析、ArkUI 联调、模拟器运行与真机验收。

二十六、发布前检查

检查三图尺寸、草稿回读、状态测试和未验证边界,再进入下一篇。

ts 复制代码
const safe = decide(snapshot, context)

二十七、交付记录

记录 draftId、平台评分和图片加载结果,保证后续问题可复核。

ts 复制代码
console.log({ requestId: context.requestId, freshness })

二十八、维护建议

策略变化时先更新纯函数测试,再更新 UI 文案和视觉 token,避免出现"显示已刷新但数据未变"的误导。

二十九、结语

稳定的菜单刷新体验来自明确状态、可回退快照和一致性校验,而不是单纯增加 loading 动画。

Additional note: keep stale content useful, visible, and honest.

三十、stale不是失败

stale 表示数据超过了建议有效期,并不意味着数据立即不可用。对阅读、导航和收藏等非破坏性操作,可以先使用旧值,再在后台刷新;对支付、删除或权限变更等高风险操作,则应在提交前重新校验服务端版本。把不同风险等级写进策略,能避免所有入口一律变灰。

三十一、读取与提交分离

菜单读取阶段可以接受 stale,真正执行动作时应再次请求最新上下文。这样用户不会因为等待刷新而失去页面位置,同时也不会把过期状态直接写回服务端。RefreshDecision 只负责呈现建议,业务服务仍是最终裁决者。

ts 复制代码
const decision = decide(snapshot, context)
const canNavigate = decision.show && decision.enabled

三十二、重试按钮的生命周期

重试动作开始时记录 retryAt 和 requestId,按钮进入 refreshing,直到成功或失败回调返回。失败后可以按退避窗口开放再次点击;如果页面已经离开,回调只更新缓存,不再更新已销毁的 UI。这样能防止重试请求和页面生命周期脱节。

三十三、版本冲突处理

当本地版本落后于服务端版本时,不应悄悄覆盖。可以提示"内容已更新,请刷新",或者合并非冲突字段后再提交。对菜单项而言,至少要保护 key、placement、权限标记和导航目标,不能因旧响应把新入口放回 primary。

ts 复制代码
if (remote.version > snapshot.version) markStale(snapshot)

三十四、多设备缓存键

手机、平板和横屏窗口可能使用不同的菜单布局。缓存键除了业务 key,还应包含 deviceClass、windowWidthBucket 和账号权限摘要。切换设备形态时可以复用业务数据,但要重新计算 placement 和可见数量。

三十五、断网场景

断网时保留 stale 内容可以让用户继续浏览已有页面;需要联网的动作应显示原因并提供稍后重试。不要把断网直接当成 missing,否则每次短暂网络波动都会让首层菜单消失。

ts 复制代码
const offline = context.hasError && !context.hasData

三十六、与骨架屏的协同

只有 missing 且正在刷新时才展示完整骨架。stale 刷新使用局部进度或时间标记,保留菜单文字和图标。骨架结束后要平滑替换,不要重建整个 Navigation 树,避免焦点和滚动位置丢失。

三十七、Focus与返回路径

刷新期间禁用菜单项时,焦点应移动到仍可操作的兄弟项或刷新按钮。刷新完成后恢复原焦点;如果入口被权限策略隐藏,应把焦点交给 More 或页面标题。系统返回始终回到真实上一页,不能因为 stale 状态插入虚拟路由。

三十八、无障碍状态更新

无障碍播报应在状态从 fresh 变为 stale、从 refreshing 变为 success 或 error 时触发一次。不要在倒计时动画的每个 tick 都播报。禁用原因要短而明确,避免读出内部 requestId 和版本号。

三十九、监控与告警

可以统计 stale 命中率、刷新耗时、重试成功率和版本冲突数。当 stale 比例持续升高,说明缓存有效期或后端稳定性需要重新评估;当冲突数升高,说明客户端提交前的版本校验不足。监控字段只使用匿名动作 id。

四十、代码评审重点

评审时重点看四处:是否所有异步回调校验 requestId,是否把 stale 与 missing 混用,是否在导航前再次确认 enabled,是否为失败提供可恢复路径。若策略判断散落在多个 Builder 中,应合并到服务层纯函数。

四十一、纯函数测试边界

测试时间边界时覆盖 updatedAt 等于 now、等于 now-maxAge、刚好超过 maxAge 三种情况;测试版本时覆盖相同 key 相同版本、相同 key 不同版本和不同 key。测试失败回退时分别验证有旧数据和无旧数据。

ts 复制代码
assert.equal(freshnessOf(item, item.updatedAt + maxAge, maxAge), 'fresh')

四十二、官方资料核对

Navigation 菜单属性、页面路由和无障碍行为以华为官方 API 参考与开发指南为准。本文的 StalePolicy.ts 是应用侧抽象,不应被当成系统 API 名称;接入时要根据项目实际声明式组件和生命周期调整。

四十三、最终边界

本文未执行 HarmonyOS 构建、系统 EasyGo 或 Navigation 解析、ArkUI 联调、模拟器运行与真机验收。示例测试只验证策略函数;正式工程仍需补充权限、深色模式、窗口变化、安装启动和卸载流程。

四十四、交付留档

把 draftId、平台评分、三图 URL、编辑器加载结果和未执行项目写入 verification.json,下一篇开始前先确认本篇状态为 completed_draft_verified。

ts 复制代码
const handoff = { draftId, score, publish: false }

stale 数据策略的核心是"可用但诚实":保留用户路径,明确更新时间,刷新失败可重试,提交前校验一致性。这样 Navigation 菜单不会因网络抖动忽隐忽现,也不会把过期数据伪装成最新结果。

四十五、刷新触发时机

刷新可以由页面首次出现、用户下拉、返回前台和定时器触发,但这些触发源都应进入同一个队列。队列先合并相同 query,再为每次实际请求生成 requestId。这样不会因为 onPageShow 和用户点击同时发生而发出两次完全相同的请求,也不会让旧请求覆盖新结果。

四十六、更新中的操作收口

当 stale 项正在刷新时,查看类操作可以继续使用快照;修改类操作应暂时禁用,直到版本校验完成。若产品要求立即响应,可以先进入确认页,再在提交前拉取最新数据。不要把"刷新中"简单映射为所有入口 disabled,应该按动作风险分级。

ts 复制代码
const allowRead = decision.show
const allowWrite = decision.enabled && !decision.refreshing

四十七、时间源与时钟漂移

如果客户端时间不可靠,不能单独依赖本地 now 判定 stale。可以把服务端时间或响应中的 expiresAt 作为参考,并在上下文中记录时钟来源。离线期间只做保守判定,恢复网络后再校正缓存时间,避免用户修改系统时间导致入口长期保持 fresh。

四十八、分页和增量刷新

菜单数据可能来自分页或增量接口。增量响应只能更新对应 key,不能直接替换整个快照集合。更新后重新计算受影响动作的 freshness 和 version,其余动作保持原状态。这样 More 中的其他入口不会因一项更新而出现无关闪烁。

四十九、回滚策略

当新版本菜单配置出现异常时,可以回滚到上一份仍在有效期内的快照,并把策略版本标记为 rollback。回滚不等于忽略错误,下一次刷新仍应携带失败原因和版本信息,供日志与监控定位。用户看到的提示应保持简短,不暴露内部发布流程。

五十、代码组织建议

把 MenuSnapshot、RefreshContext 和 RefreshDecision 放在独立模型文件,把请求和缓存放在 Service,把纯函数放在 policy 模块。页面只订阅解析后的状态,避免在 Builder 中读取系统时间、拼装错误文案或比较版本号。这样的边界有助于单元测试,也更容易适配不同设备。

ts 复制代码
export interface RefreshService {
  load(key: string): Promise<MenuSnapshot>
  retry(key: string): Promise<MenuSnapshot>
}

51、验收记录

验收时记录 fresh、stale、missing 三类截图,记录刷新成功与失败的时间线,确认双击只产生一个 requestId。检查横屏和平板布局是否保持首层稳定,确认无障碍播报没有连续重复。本文的本地测试和平台验证不替代目标工程的编译、联调和真机验收。

52、交接说明

下一位维护者只需读取 article.json 与 verification.json,就能知道当前草稿 id、评分、图片状态和未验证边界。若修改正文或图片,应重新渲染、上传、回读和评分,不要直接覆盖已经核验的结果文件。 每次变更都应留下新的验证记录。 以便问题可追踪、结果可复核。 并支持后续回滚。 保持历史可读。 便于审计。 可回查。 可复现。 可交接。 完成。 好。 行。

相关推荐
c2892480843s12 小时前
【鸿蒙心迹】网络请求封装实战:RCP 选型、拦截器与弱网预算
移动开发·网络请求·harmonyos·鸿蒙·rcp
HarmonyOS_SDK16 小时前
【剪贴板体验优化】前置预校验,航旅纵横权限弹窗体验更智能
harmonyos
贾伟康18 小时前
【共创稿事节】HarmonyOS 7.0 Progress进度体系:从接入到生产验证
性能优化·实战·harmonyos·arkts·arkui
贾伟康1 天前
HarmonyOS API 26 HMAF Skill 端侧 A2A 消息去重与上下文最小化
harmonyos·arkts·skill·a2a·hmaf
贾伟康1 天前
HarmonyOS API 26 HMAF Skill 多 Skill 冲突仲裁与可恢复执行
harmonyos·arkts·智能体·skill·hmaf
钉先生贾1 天前
毕业项目:从上游更新到回归发布的完整维护闭环
华为·harmonyos
行者-全栈开发1 天前
【鸿蒙心迹】权限申请全流程——从声明到用户授权的完整链路(HarmonyOS 7.x)
华为·harmonyos
KevinChaos2 天前
阳台小花园开发手记 01|HarmonyOS 7.0 互动卡片:小花园为何要两套界面
华为·harmonyos
Ranger09292 天前
下一代鸿蒙打包工具链:arkdown
harmonyos