HarmonyOS应用实战-启示散页-70-StorageLink 回归别只测页面:覆盖水合顺序、空值与重进路径
HarmonyOS 应用实战 70:StorageLink 回归别只测页面,覆盖水合顺序、空值与重进路径
StorageLink 的问题经常不是“页面打不开”,而是时序问题:首次挂载拿到默认值,切换题库后某个 Builder 对未水合对象调用toString,删除题库后旧 id 又被重进页面读取。只点一遍页面,测不到这些边界。
本文解决四个问题:
- 把 StorageLink 回归拆成水合、空值、重进三类
- 避免 Builder 里直接使用未守卫对象
- 新增 key 时补齐启动、重置和删除链路
- 用脚本检查危险访问模式
StorageLink 不是持久化本身
它只是 AppStorage 和页面状态的绑定。真正持久化仍在 Preferences。回归时必须同时看启动水合、页面绑定和持久化写入。
故障链:新增 @StorageLink key -> 启动未 setOrCreate -> 页面 Builder 读取 undefined -> 调用 toString 崩溃StorageLink是页面和AppStorage的绑定,不是持久化协议。把它当最终事实,就会在首次挂载、清空数据和重进时踩空值。
新增 key 要有生命周期清单
每个 StorageLink key 都要说明默认值、启动水合、写入 owner、重置路径和删除路径。
interfaceStorageLinkKeySpec<T>{key:string;defaultValue:T;hydrateOwner:string;writeOwner:string;resetIncluded:boolean;}新增 key 要有完整生命周期。定义、默认值、水合、消费、重置、删除缺一项,回归时就可能只在某条路径崩。
启动时先 setOrCreate,再挂页面
新增 key 后,如果只在页面里声明 StorageLink,首次进入就可能拿到 undefined。
classAppStorageHydrator{hydrate():void{AppStorage.setOrCreate('currentDeckId',DEFAULT_DECK_ID);AppStorage.setOrCreate('deck.changedAt',0);AppStorage.setOrCreate('favorite.changedAt',0);AppStorage.setOrCreate('questionHistory.changedAt',0);}}页面挂载前先setOrCreate,能保证@StorageLink至少拿到安全默认值。持久化恢复再覆盖默认值,顺序不能反过来。
Builder 里不要直接调用可空对象方法
ArkUI Builder 中应先通过方法取安全默认值,避免undefined.toString()或数组.length崩溃。
@Componentstruct FavoriteBadge{@StorageLink('favorite.count')privatefavoriteCount:number|undefined=0;privatesafeFavoriteCount():number{returnthis.favoriteCount??0;}build(){Text(`${this.safeFavoriteCount()}`);}}@Builder里不要直接调用可空对象方法。数组、对象和字符串都先经过安全方法转换,再进入 UI 表达式。
重置和删除要同步 key 生命周期
清空历史、删除题库、退出演示模式时,如果只清 Preferences 不更新 AppStorage,页面会显示旧值。
classAppStateResetService{asyncclearQuestionHistory():Promise<void>{awaitQuestionHistoryRepository.saveAll([]);AppStorage.setOrCreate('questionHistory.changedAt',Date.now());AppStorage.setOrCreate('questionHistory.count',0);}}重置和删除要同步 key 生命周期,否则页面状态清掉了,AppStorage还留着旧引用;或者持久化清了,页面仍显示旧值。
回归脚本先抓危险模式
脚本不能替代真机,但能快速发现 Builder 中直接访问可空值的写法。
rg-n"@StorageLink|\.toString\(|\.length|\$\{.*Storage"D:\ProgramData\huawei\lesson\The_Book_of_Answers\entry\src\main\ets D:\ProgramData\huawei\lesson\The_Book_of_Answers\libraryHSP\src\main\ets危险模式扫描适合放在回归前。它不能证明运行无 bug,但能快速找出.length、模板字符串和未守卫对象这类高危写法。
StorageLink 回归矩阵
回归要覆盖首次启动、切换题库、清空历史、删除当前题库、重进页面。
| 路径 | 检查点 |
|---|---|
| 首次启动 | key 已 setOrCreate |
| 切换题库 | changedAt 通知相关页 |
| 清空历史 | count 和列表都归零 |
| 删除当前题库 | currentDeckId 回退 |
| 重进页面 | 不读取旧 undefined |
回归矩阵要覆盖水合顺序、空值、重进和删除路径。只点当前页面一次,测不到StorageLink最容易出问题的时序边界。
给每个 StorageLink key 建生命周期表
@StorageLink出问题时,往往不是单个页面错,而是 key 的生命周期缺一段。新增 key 时就应该写表:谁定义、默认值是什么、何时水合、哪些页面消费、重置和删除时怎么处理。
| key | 默认值 | 水合 owner | 消费页面 | 重置/删除 |
|---|---|---|---|---|
home.searchHistory | [] | Persist.hydrate() | 首页、搜索面板 | 清历史时同步清空 |
currentDeckId | 默认题库 id | 启动状态水合 | 首页、选择器、抽取页 | 删除题库时回退 |
favorite.changedAt | 0 | 收藏服务写入 | 收藏页、首页卡片 | 清收藏时更新 |
生命周期表比单次页面测试更有价值,因为它能直接发现“定义了但没水合”“清了持久化但没清 AppStorage”这类问题。
Builder 消费前先变成安全值
ArkUI Builder 里最容易出现的危险写法,是直接对@StorageLink数组或对象做.length、模板字符串或.toString()。建议先用普通方法返回安全默认值,再进入 UI 表达式。
privategetSafeHistoryCount():number{returnArray.isArray(this.searchHistory)?this.searchHistory.length:0;}privategetCurrentDeckLabel():string{returnthis.currentDeckName&&this.currentDeckName.length>0?this.currentDeckName:'默认题库';}这里用普通方法而不是 Builder 里的临时语句,是为了让空值处理集中、可搜索、可单独复查。
回归矩阵要覆盖水合、空值、重进和清理
StorageLink回归不能只看当前页面是否显示。更有效的矩阵是:首次安装、普通冷启动、清空 Preferences、删除当前题库、切换 Tab 后返回、重置应用数据。每条路径都看页面是否读取安全值。
| 路径 | 要观察 | 失败时先查 |
|---|---|---|
| 首次安装 | key 已有默认值 | AppStorage.setOrCreate是否早于页面 |
| 清空数据 | 页面不崩溃 | Builder 是否直接取空对象 |
| 删除题库 | currentDeckId 回退 | 删除流程是否同步 key |
| Tab 重进 | 展示最终事实 | 页面是否只改局部状态 |
没有真机或模拟器运行记录时,文章只能写静态回归清单和危险模式扫描,不能声称运行崩溃已完全覆盖。
危险模式扫描要配合人工判断
扫描.length、模板字符串和.toString()不是为了禁止所有用法,而是为了找出@StorageLink值未经守卫就进入 Builder 的位置。命中后要看变量来源:普通局部数组可以继续用,StorageLink 数组就要改成安全方法。
rg-n"@StorageLink|\.length|\.toString\(|`\$\{"entry hsp har检查时建议把结果分成三类:安全局部变量、已守卫的 StorageLink、未守卫的 StorageLink。只有第三类是必须修的风险。这样文章不会变成“禁止使用 length”的误导,而是教读者识别真正的状态时序问题。
人工评审时把每个 key 走一遍删除路径
StorageLink的删除路径经常被漏测。评审时不要只看启动水合,还要看清空历史、删除题库、清空收藏、重置应用数据时对应 key 是否同步更新。
删除路径检查: home.searchHistory -> 清空历史后 AppStorage 为 [] currentDeckId -> 删除当前题库后回退到可用 id favorite.changedAt -> 清空收藏后刷新信号更新 diagnostics.changedAt -> 清空诊断账本后页面重新读取这一步能发现“页面看起来清了,但 AppStorage 里仍有旧值”的问题。第 70 篇的重点不是记住某个装饰器,而是把 key 的生老病死都收进一张可复查清单。
小结
StorageLink 回归不能只看页面能否打开。新增 key 要补齐启动水合、默认值、写入 owner、重置路径和删除路径;Builder 只使用安全方法读取值,才能覆盖首次挂载、空值和重进路径。
