第 19 天|Preferences:轻量缓存与状态恢复

文章来源声明: 原文作者:木子雨廷; 来源站点:掘金; 原文链接:https://juejin.cn/post/7688644921476415530; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

适合正在做鸿蒙本地缓存选型的开发者。用服务层封装加异常兜底的写法,能直接迁移到点赞、开关、引导标记等真实场景,避免缓存方案后期难以替换。

##### 本篇看点
  • 阅读目标:理解 Preferences 的轻量键值边界,并和 RDB 的业务列表场景区分开。
  • 核心问题:Preferences 适合轻量键值,不适合承载复杂业务列表。
  • 对比迁移:对应 iOS UserDefaults 和 Flutter shared_preferences。
前言
本地缓存可以先区分数据形态。主题、语言、开关、点赞状态、首次启动标记,这些轻量键值数据,用 Preferences 更合适。
iOS 里对应 UserDefaults,Flutter 里常用 shared_preferences。HarmonyOS 的 Preferences 也是这类能力:轻量、键值、适合小数据。
一. Preferences 适合什么
适合:
  1. 暗黑模式选择。
  2. 多语言选择。
  3. 用户设置开关。
  4. 文章点赞 id 集合。
  5. 搜索历史少量数据。
  6. 首次启动引导标记。
不适合:
  1. 大列表。
  2. 下载记录。
  3. 播放历史。
  4. 复杂查询。
  5. 多表关系。
后面这些应该交给 RDB。
二. 用服务层封装 Preferences
页面直接调用系统 API 会让代码很难测,也很难替换缓存方案。建议先抽一个接口:
<span>export</span> <span>interface</span> <span>KeyValueStorage</span> {
  <span>getString</span>(<span>key</span>: <span>string</span>, <span>defaultValue</span>: <span>string</span>): <span>Promise</span><<span>string</span>>;
  <span>putString</span>(<span>key</span>: <span>string</span>, <span>value</span>: <span>string</span>): <span>Promise</span><<span>void</span>>;
  <span>getBoolean</span>(<span>key</span>: <span>string</span>, <span>defaultValue</span>: <span>boolean</span>): <span>Promise</span><<span>boolean</span>>;
  <span>putBoolean</span>(<span>key</span>: <span>string</span>, <span>value</span>: <span>boolean</span>): <span>Promise</span><<span>void</span>>;
}

页面或 Store 只依赖接口,不关心底层是 Preferences、内存 mock,还是后续迁移到安全存储。
三. 点赞缓存怎么设计
资讯列表里的点赞状态适合 Preferences,因为它是小规模键值状态。
<span>class</span> <span>LikeStore</span> {
  <span>private</span> <span>likedIds</span>: <span>Set</span><<span>string</span>> = <span>new</span> <span>Set</span><<span>string</span>>();

  <span>async</span> <span>load</span>(<span></span>) {
    <span>const</span> raw = <span>await</span> <span>this</span>.<span>storage</span>.<span>getString</span>(<span>'liked_article_ids'</span>, <span>'[]'</span>);
    <span>const</span> <span>ids</span>: <span>string</span>[] = <span>JSON</span>.<span>parse</span>(raw) <span>as</span> <span>string</span>[];
    <span>this</span>.<span>likedIds</span> = <span>new</span> <span>Set</span><<span>string</span>>(ids);
  }

  <span>async</span> <span>toggle</span>(<span>id: <span>string</span></span>) {
    <span>if</span> (<span>this</span>.<span>likedIds</span>.<span>has</span>(id)) {
      <span>this</span>.<span>likedIds</span>.<span>delete</span>(id);
    } <span>else</span> {
      <span>this</span>.<span>likedIds</span>.<span>add</span>(id);
    }
    <span>await</span> <span>this</span>.<span>storage</span>.<span>putString</span>(<span>'liked_article_ids'</span>, <span>JSON</span>.<span>stringify</span>(<span>Array</span>.<span>from</span>(<span>this</span>.<span>likedIds</span>)));
  }
}

这里有两层状态:内存里的 Set 用于快速判断,Preferences 用于跨启动恢复。
四. UI 局部刷新怎么配合
点赞按钮的刷新范围可以尽量小。把点赞状态收敛到单个 item 或按钮组件,列表会更稳:
<span>LikeBurstButton</span>({
  <span>liked</span>: item.<span>liked</span>,
  <span>onToggle</span>: <span>() =></span> {
    <span>this</span>.<span>onToggleLike</span>(item.<span>id</span>);
  }
})

Store 更新某个 item 后,只让对应行重新渲染。这个案例很适合和 @Observed/@ObjectLink 或不可变 item 更新一起讲。
五. Preferences 也要处理异常
本地缓存读写看起来稳定,但仍然可能失败,比如 JSON 格式损坏、版本升级字段变化。
所以读取时要有兜底:
<span>function</span> <span>parseStringArray</span>(<span>raw: <span>string</span></span>): <span>string</span>[] {
  <span>try</span> {
    <span>const</span> value = <span>JSON</span>.<span>parse</span>(raw) <span>as</span> <span>string</span>[];
    <span>return</span> <span>Array</span>.<span>isArray</span>(value) ? value : [];
  } <span>catch</span> (_) {
    <span>return</span> [];
  }
}

真实项目里还可以加版本号,方便以后迁移。
六. 三端对比
场景HarmonyOSFlutteriOS
轻量键值Preferencesshared\_preferencesUserDefaults
大量结构化数据RDBsqflite / driftSQLite / CoreData
安全敏感数据安全存储策略flutter\_secure\_storageKeychain
所以选型思路很简单:小设置用键值,业务列表用数据库,敏感数据用安全存储。
七. 实践经验
第一,把完整列表 JSON 全塞进 Preferences,后期查询和迁移都痛苦。
第二,页面到处直接读写 key,key 名无法统一管理。
第三,只改内存状态,忘了持久化,重启后丢失。
第四,读取缓存没有异常兜底,一次坏数据导致页面打不开。
小结
Preferences 是鸿蒙本地缓存的第一站。它最适合轻量设置和少量状态,配合 Store 可以做出很自然的跨启动恢复。
下一篇我们讲 RDB,把播放历史、订阅、下载记录这些真正的业务数据放进结构化存储里。
今日练习
  1. 用 Preferences 缓存一个暗黑模式开关,重启后恢复。
  2. 用字符串数组保存点赞 id 集合,并处理 JSON 解析失败兜底。
  3. 说明为什么收听历史不适合直接塞进 Preferences。