文章导读
- Preferences 负责落盘,AppStorage 负责页面响应式镜像。
- 数组修改必须先 slice 复制,再 setOrCreate 写回,避免 UI 感知不到变更。
- 收藏、关注、浏览历史和浏览量都通过 PrefStore 统一维护。
页面效果
收藏、关注、历史和浏览量应在页面切换后保持一致,重启应用后仍能恢复,用户不会感觉数据丢失。
实战拆解
本地状态是移动应用绕不开的部分。羽球联盟没有把 Preferences 调用散落到各页面,而是用 PrefStore 统一管理。页面只调用 toggleFavorite、toggleFollow、addHistory、login、logout 等方法,持久化细节藏在 store 内部。
这篇文章最重要的技术点是:AppStorage.get 返回的数组不能原地 splice、push、unshift 后就期待 UI 刷新。正确做法是先 slice 得到副本,修改副本,再 AppStorage.setOrCreate 写回。项目里的 toggle 和 addHistory 都遵循这个模式。
Preferences 与 AppStorage 的关系可以解释成“磁盘真相 + 内存响应式镜像”。启动时hydrate()把磁盘数据恢复到 AppStorage;用户操作时先更新 AppStorage 保证 UI 立即变化,再写 Preferences 持久化。这样体验和可靠性都比较平衡。
HarmonyOS 官方文档里,Preferences 适合保存轻量级键值数据,而 AppStorage 适合在页面之间共享响应式状态。把两个能力拆开用,既能保留 ArkUI 的联动体验,也不会让页面层直接承担序列化和落盘细节。这里可以对照官方 API 说明:Preferences 与 AppStorage。
项目里还有一个很实用的细节:PrefStore会先根据登录态切换scope,再把favorites、history、follows和viewCountsJson重新加载到 AppStorage。这样游客态和登录态看到的是不同数据空间,个人中心的统计数字也能跟随账号切换即时刷新。
关键代码
private async toggle(key: string, id: string): Promise<boolean> { const original: string[] = AppStorage.get<string[]>(key) ?? []; const next: string[] = original.slice(); const idx: number = next.indexOf(id); const added: boolean = idx < 0; if (added) { next.unshift(id); } else { next.splice(idx, 1); } AppStorage.setOrCreate<string[]>(key, next); await this.persist(key, next); return added; } async addHistory(id: string): Promise<void> { const original: string[] = AppStorage.get<string[]>('history') ?? []; const next: string[] = original.slice(); const idx: number = next.indexOf(id); if (idx >= 0) { next.splice(idx, 1); } next.unshift(id); if (next.length > 100) { next.length = 100; } AppStorage.setOrCreate<string[]>('history', next); await this.persist('history', next); await this.incrementView(id); }这段实现把“收藏开关”和“浏览历史去重 + 头插 + 截断”放在同一个 store 中处理。页面只感知结果,真正负责一致性的还是slice -> 修改副本 -> setOrCreate -> persist这一条链路。
取舍分析
这里的核心取舍不是“要不要持久化”,而是“让谁负责持久化”。如果每个页面都各自调用 Preferences,短期写起来很快,但收藏、历史和统计数字迟早会出现不同步。把更新入口集中到PrefStore后,页面层只负责触发动作,状态边界会清晰很多。
另一个取舍是性能和体验的平衡。这里没有在每次点击时重新hydrate()全量数据,而是只更新当前受影响的 AppStorage 键,然后异步落盘。对用户来说,收藏按钮和历史列表会立即变化;对工程来说,Preferences 仍然保存着重启后可恢复的最终结果。
设计落点
- Preferences 负责持久化,AppStorage 负责页面响应式,两者职责不同。
- 数组状态需要复制后写回,不能指望原地修改触发所有视图刷新。
- 统一 store 能避免多个页面各自读写造成状态竞争。
- 历史记录在写入时顺手维护浏览量,可以少做一次额外的页面埋点同步。
易踩坑
- 不要在多个页面直接操作同一个 Preferences 文件,状态会难以同步。
- 不要直接持久化 Resource 或 class 实例,保存纯 JSON 更稳。
- 不要把
AppStorage.get()拿到的数组当成普通局部变量原地改动,@StorageLink很可能收不到刷新信号。
验证方式
- 收藏后重启应用,确认状态仍在。
- 清空历史后确认个人中心统计同步归零。
- 连续切换页面,确认数字不会延迟刷新。
- 游客态添加收藏后再登录其他账号,确认两个数据空间不会互相覆盖。
小结
Preferences 结合 AppStorage 的价值,不只是“把数据存下来”,而是把页面响应、账号隔离和持久化入口统一到一个地方。对羽球联盟这样的内容型应用来说,这种组织方式能明显降低收藏、历史和统计状态失真的概率,也更适合作为后续设置项、草稿箱或离线缓存能力的基础。