Harmony 状态一致性:页面显示 8 点,校验为什么按 9 点?

在 HarmonyOS 菁英班里,我们小组做过一个健康生活打卡 App。其中一个问题我一直记得:目标时间改成了 8 点,页面显示的也是 8 点,打卡校验却还在按默认的 9 点算。

页面看起来已经改好了,业务逻辑却没跟上。现在回头看,问题出在两份“目标时间”各管各的。我把当时的排查和后来整理的想法记在这里。下面的代码是单独写的解释示例,只涉及业务状态,没有包含完整的 ArkUI 页面。

显示改了,校验读的还是旧值

环节 当时观察到的情况 要追问的问题
选择时间 用户修改目标时间 回调更新了哪个变量?
页面展示 显示用户选择的 8 点 展示值从哪里来?
业务校验 仍按默认 9 点判断 校验是否读取了同一个值?

当时结合 HiLog 和页面提醒往下查,找到了两条路径:页面读 selectedTime,校验读 currentTime。选择器改了前者,后者还停在默认值。

currentTime 是当时记录里的名字,单看名称也不太容易猜出它参与了哪种判断。下面的示例直接区分“目标时间”和“实际打卡时间”,读起来清楚一些。

1
2
3
4
5
6
7
8
9
原来的路径
选择器 → selectedTime → 页面显示 8 点
校验器 → 另一份默认值 → 仍按 9 点判断

希望形成的路径
选择器 → 当前任务的目标时间
├─ 页面从这里读
├─ 校验从这里读
└─ 保存时也写这一份

这次让我记住了一点:看到页面变了,还得看看校验和保存到底读的是哪份数据。

我会把目标时间放到一处

如果再写一次,我会让显示和校验都读取任务里的目标时间。这里用“从零点开始的分钟数”表示时间,省去字符串和时间对象之间的来回转换;跨日和时区问题还需要另外处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Task = {
targetMinutes: number;
};

function formatTarget(task: Task): string {
const hour = Math.floor(task.targetMinutes / 60);
const minute = task.targetMinutes % 60;
return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
}

function isOnTime(task: Task, checkInMinutes: number): boolean {
return checkInMinutes <= task.targetMinutes;
}

// 用户把目标时间改为 08:00。
const task: Task = { targetMinutes: 8 * 60 };
// 展示与校验读取同一个 task.targetMinutes。

接到 ArkUI 页面时,还要按项目的 SDK 版本选择状态管理方式。状态装饰器能让界面随数据更新,但如果自己维护了两份目标时间,它不会自动把两份数据合起来。官方状态管理文档可以配合着看。

改完还得退出页面再试试

场景 需要验证什么
修改目标时间 展示、校验都使用新值
取消编辑 没有把未确认的草稿写回正式任务
保存后重新进入 持久化值和页面值一致
App 重启 从存储恢复业务状态,再派生显示
首次启动或缺少任务数据 显示可解释的错误,避免把初始化失败统一报成网络错误

同一个 App 里还遇到过另一件事:编辑跑步任务时提示“网络连接错误”,最后发现是初始化数据没有入库。开发时通过重装触发初始化,暂时恢复了正常。

现在看,这个提示本身就容易把排查方向带偏。继续完善时,我会先把数据缺失和网络失败分开,再处理初始化重复执行的问题。总不能让用户每次遇到问题都重装一遍。

下次遇到类似问题,先查这几处

  1. 重现一条最小操作路径,记录选择、显示、校验三个值。
  2. 对照读写位置,检查是否存在语义相同、分别维护的状态。
  3. 将显示、校验与保存统一到明确的业务状态。
  4. 覆盖取消、重入、重启和首次初始化,验证完整生命周期。

以后再遇到“界面是对的,结果却不对”,我会先沿着读写位置查一遍。两个变量都叫时间,并不意味着它们一直同步。

Buy me a coffee