@buaa_smat/hometrans 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +141 -124
  2. package/agents/build-fixer.md +1 -0
  3. package/agents/code-review-fix.md +1 -0
  4. package/agents/code-reviewer.md +1 -0
  5. package/agents/logic-coding.md +1 -0
  6. package/agents/logic-context-builder.md +1 -0
  7. package/agents/review-fixer.md +1 -0
  8. package/agents/self-test-fixer.md +1 -0
  9. package/agents/self-tester.md +260 -233
  10. package/agents/spec-generator.md +1 -0
  11. package/agents/test-tools/autotest/README.md +223 -0
  12. package/agents/test-tools/autotest/config.yaml.example +58 -0
  13. package/agents/test-tools/autotest/pyproject.toml +16 -0
  14. package/agents/test-tools/autotest/report_tool.py +759 -0
  15. package/agents/test-tools/autotest/self_test_runner.py +773 -0
  16. package/agents/test-tools/autotest/testcases_schema.md +143 -0
  17. package/agents/test-tools/autotest/testcases_tool.py +215 -0
  18. package/agents/test-tools/autotest/uv.lock +3156 -0
  19. package/agents/test-tools/harmony_autotest-0.1.0-py3-none-any.whl +0 -0
  20. package/agents/test-tools/hypium-6.1.0.210-py3-none-any.whl +0 -0
  21. package/agents/test-tools/hypium_mcp-0.6.5-py3-none-any.whl +0 -0
  22. package/agents/test-tools/xdevice-6.1.0.210-py3-none-any.whl +0 -0
  23. package/agents/test-tools/xdevice_devicetest-6.1.0.210-py3-none-any.whl +0 -0
  24. package/agents/test-tools/xdevice_ohos-6.1.0.210-py3-none-any.whl +0 -0
  25. package/dist/cli/config-store.js +27 -2
  26. package/dist/cli/config.js +17 -6
  27. package/dist/cli/index.js +3 -2
  28. package/dist/cli/init.js +135 -22
  29. package/dist/cli/mcp.js +2 -2
  30. package/dist/context/index.js +165 -69
  31. package/package.json +59 -60
  32. package/skills/code-dev-review-fix/SKILL.md +279 -0
  33. package/skills/code-dev-review-fix-workspace/evals/evals.json +56 -0
  34. package/skills/code-dev-review-fix-workspace/iteration-1/routing-results.md +23 -0
  35. package/skills/convert_pipeline/SKILL.md +423 -439
  36. package/skills/hmos-resources-convert/SKILL.md +623 -0
  37. package/skills/hmos-resources-convert/evals/evals.json +171 -0
  38. package/skills/hmos-resources-convert/references/conversion-rules.md +663 -0
  39. package/skills/hmos-resources-convert/references/dependency-analysis-rules.md +388 -0
  40. package/skills/hmos-resources-convert/references/resource-mapping-rules.md +457 -0
  41. package/skills/hmos-resources-convert/references/xml-drawable-to-svg-rules.md +513 -0
  42. package/skills/hmos-resources-convert/template/AppScope/app.json5 +10 -0
  43. package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +8 -0
  44. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
  45. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
  46. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +7 -0
  47. package/skills/hmos-resources-convert/template/build-profile.json5 +42 -0
  48. package/skills/hmos-resources-convert/template/code-linter.json5 +32 -0
  49. package/skills/hmos-resources-convert/template/entry/build-profile.json5 +33 -0
  50. package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +6 -0
  51. package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +23 -0
  52. package/skills/hmos-resources-convert/template/entry/oh-package.json5 +10 -0
  53. package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +48 -0
  54. package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +16 -0
  55. package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +23 -0
  56. package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +55 -0
  57. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +8 -0
  58. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +8 -0
  59. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +16 -0
  60. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
  61. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
  62. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +7 -0
  63. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
  64. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +3 -0
  65. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +5 -0
  66. package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +8 -0
  67. package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +2 -0
  68. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +35 -0
  69. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +5 -0
  70. package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +16 -0
  71. package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +5 -0
  72. package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +33 -0
  73. package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +23 -0
  74. package/skills/hmos-resources-convert/template/hvigorfile.ts +6 -0
  75. package/skills/hmos-resources-convert/template/oh-package-lock.json5 +28 -0
  76. package/skills/hmos-resources-convert/template/oh-package.json5 +10 -0
  77. package/skills/hmos-resources-convert/tools/apktool.bat +85 -0
  78. package/skills/hmos-resources-convert/tools/apktool_3.0.1.jar +0 -0
  79. package/skills/hmos-ui-align/SKILL.md +182 -0
  80. package/skills/hmos-ui-align/config-example.json +11 -0
  81. package/skills/hmos-ui-align/config.json +11 -0
  82. package/skills/hmos-ui-align/diff_analysis.md +53 -0
  83. package/skills/hmos-ui-align/page_align.md +62 -0
  84. package/skills/hmos-ui-align/readme.md +231 -0
  85. package/skills/hmos-ui-align/references/Comparison_Template.md +2 -0
  86. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
  87. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
  88. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
  89. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
  90. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
  91. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
  92. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
  93. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
  94. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  95. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
  96. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217V1.md +911 -0
  97. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -0
  98. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
  99. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
  100. package/skills/hmos-ui-align/references/UI_Analysis_Template.md +4 -0
  101. package/skills/hmos-ui-align/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2535 -0
  102. package/skills/hmos-ui-align/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
  103. package/skills/hmos-ui-align/references/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
  104. package/skills/hmos-ui-align/scripts/app_feature_verify.py +443 -0
  105. package/skills/hmos-ui-align/scripts/navigation-capure.md +37 -0
  106. package/skills/hmos-ui-align/scripts/page_capture.py +592 -0
  107. package/skills/hmos-ui-align-batch/SKILL.md +99 -0
  108. package/skills/hmos-ui-align-batch/references/conversion-procedure.md +180 -0
  109. package/skills/hmos-ui-align-batch/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2535 -0
  110. package/skills/hmos-ui-align-batch/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
  111. package/skills/hmos-ui-align-batch/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
  112. package/skills/hmos-ui-align-batch/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
  113. package/skills/hmos-ui-align-batch/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
  114. package/skills/hmos-ui-align-batch/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
  115. package/skills/hmos-ui-align-batch/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
  116. package/skills/hmos-ui-align-batch/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
  117. package/skills/hmos-ui-align-batch/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
  118. package/skills/hmos-ui-align-batch/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
  119. package/skills/hmos-ui-align-batch/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
  120. package/skills/hmos-ui-align-batch/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  121. package/skills/hmos-ui-align-batch/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
  122. package/skills/hmos-ui-align-batch/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -0
  123. package/skills/hmos-ui-align-batch/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
  124. package/skills/hmos-ui-align-batch/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
  125. package/skills/hmos-ui-align-batch/scripts/android_parse_fast.py +1606 -0
  126. package/skills/self-test/SKILL.md +369 -0
  127. package/skills/self-test/readme.md +309 -0
  128. package/skills/spec-generator-skill/SKILL.md +332 -0
  129. package/skills/spec-generator-skill/references/android-platform-tokens.md +105 -0
  130. package/skills/spec-generator-skill/references/spec-sample-1.md +78 -0
  131. package/skills/spec-generator-skill/references/spec-sample-2.md +58 -0
  132. package/skills/spec-generator-skill/references/spec-sample-3.md +116 -0
  133. package/skills/spec-generator-skill/references/step4-report-template.md +33 -0
  134. package/agents/self-test-setup.md +0 -165
  135. package/dist/context/resources/sdkConfig.json +0 -24
  136. package/src/context/resources/sdkConfig.json +0 -24
@@ -0,0 +1,903 @@
1
+ # AppStorage:应用全局的UI状态存储
2
+ 在阅读本文档前,建议提前阅读:[状态管理概述](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management-overview),从而对状态管理框架中AppStorage的定位有一个宏观了解。
3
+
4
+ AppStorage是与应用进程绑定的全局UI状态存储中心,由UI框架在应用启动时创建,将UI状态数据存储于运行内存,实现应用级全局状态共享。
5
+
6
+ 作为应用的“中枢”,AppStorage是[持久化数据PersistentStorage](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-persiststorage)和[环境变量Environment](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-environment)与UI交互的中转桥梁。其核心价值在于为开发者提供跨ability的大范围UI状态数据共享能力。
7
+
8
+ AppStorage提供了API接口,允许开发者在自定义组件外手动触发AppStorage对应属性的增、删、改、查操作。建议配合[AppStorage API文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-state-management#appstorage)阅读。最佳实践请参考[状态管理最佳实践](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-status-management)。
9
+
10
+ > **说明:**
11
+ >
12
+ > 多组件间状态共享和同步、状态管理和UI解耦,可以参考解决方案[基于StateStore的全局状态管理开发实践](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-global-state-management-state-store)。 不涉及UI组件同步的数据处理工作,建议[通过用户首选项实现数据持久化](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/data-persistence-by-preferences)。
13
+ >
14
+ ## 概述
15
+ AppStorage是在应用启动时创建的单例,用于提供应用状态数据的中心存储。这些状态数据在应用级别可访问。AppStorage在应用运行过程中保留其属性。
16
+
17
+ AppStorage中的属性通过唯一的字符串类型属性名(key)访问,支持与UI组件同步,并可在应用业务逻辑中被访问。其支持应用的[主线程](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/thread-model-stage)内多个[UIAbility](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-ability-uiability)实例间的UI状态数据共享。
18
+
19
+ AppStorage中的属性可以被双向同步,并具有不同的功能,比如数据持久化(详见[PersistentStorage](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-persiststorage))。这些UI状态是通过业务逻辑实现,与UI解耦,如果希望这些UI状态在UI中使用,需要用到[@StorageProp](#storageprop)和[@StorageLink](#storagelink)。
20
+ ## @StorageProp
21
+ @StorageProp与AppStorage中对应的属性建立单向数据同步。
22
+
23
+ > **说明:**
24
+ >
25
+ > 从API version 11开始,该装饰器支持在元服务中使用。
26
+ >
27
+ ### 装饰器使用规则说明
28
+ | @StorageProp变量装饰器 | 说明 |
29
+ | --- | --- |
30
+ | 装饰器参数 | 常量字符串,必填(字符串需要有引号)。 **说明:** 使用null和undefined作为key时,会隐式转换为对应的字符串,不建议该用法。 |
31
+ | 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。 API Version 12及以上支持Map、Set、Date、undefined和null类型以及这些类型的联合类型,示例见[AppStorage支持联合类型](#appstorage支持联合类型)。 嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现)。 **说明:** 变量类型必须被指定,建议和AppStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。 |
32
+ | 不允许装饰的变量类型 | 不支持装饰Function类型。 |
33
+ | 同步类型 | 单向同步:从AppStorage的对应属性到组件的状态变量。 组件本地的修改是允许的,但是AppStorage中给定的属性一旦发生变化,将覆盖本地的修改。 |
34
+ | 被装饰变量的初始值 | 必须本地初始化,如果AppStorage实例中不存在属性,则用该初始值初始化该属性,并存入AppStorage中。 |
35
+ ### 变量的传递/访问规则说明
36
+ | 传递/访问 | 说明 |
37
+ | --- | --- |
38
+ | 从父节点初始化和更新 | 禁止从父节点初始化和更新@StorageProp。仅支持使用AppStorage中对应key的属性进行初始化,如果不存在对应key,则使用本地默认值进行初始化。 |
39
+ | 初始化子节点 | 支持,可用于初始化[@State](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state)、[@Link](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-link)、[@Prop](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-prop)、[@Provide](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-provide-and-consume)。 |
40
+ | 是否支持组件外访问 | 否。 |
41
+
42
+ **图1** @StorageProp初始化规则图示
43
+
44
+ ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/9d/v3/UdOEkjUVRLq5n-k5F7fhig/zh-cn_image_0000002566867929.png)
45
+ ### 观察变化和行为表现
46
+ **观察变化**
47
+
48
+ - 当装饰的类型为boolean、string、number时,可以观察到数值的变化。
49
+
50
+ - 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和属性变化(详见[从ui内部使用appstorage](#从ui内部使用appstorage))。
51
+
52
+ - 当装饰的对象是数组时,可以观察到数组添加、删除、更新数组单元的变化。
53
+
54
+ - 当装饰的对象是Date时,可以观察到Date整体的赋值,以及通过调用Date的接口setFullYear、setMonth、setDate、setHours、setMinutes、setSeconds、setMilliseconds、setTime、setUTCFullYear、setUTCMonth、setUTCDate、setUTCHours、setUTCMinutes、setUTCSeconds、setUTCMilliseconds更新Date的属性。详见[装饰Date类型变量](#装饰date类型变量)。
55
+
56
+ - 当装饰的变量是Map时,可以观察到Map整体的赋值,以及通过调用Map的接口set、clear、delete更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。
57
+
58
+ - 当装饰的变量是Set时,可以观察到Set整体的赋值,以及通过调用Set的接口add、clear、delete更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。
59
+
60
+ **框架行为**
61
+
62
+ - @StorageProp(key)装饰的数值发生变化,不会同步写回AppStorage对应的属性;变化会触发自定义组件重新渲染,并且该变动仅作用于当前组件的私有成员变量,其他绑定该key的数据不会同步改变。
63
+
64
+ - 当AppStorage中对应key的属性发生改变时,所有@StorageProp(key)装饰的变量都会同步更新,本地的修改将被覆盖。
65
+ ## @StorageLink
66
+ @StorageLink与AppStorage中对应的属性建立双向数据同步。
67
+
68
+ > **说明:**
69
+ >
70
+ > 从API version 11开始,该装饰器支持在元服务中使用。
71
+ >
72
+ ### 装饰器使用规则说明
73
+ | @StorageLink变量装饰器 | 说明 |
74
+ | --- | --- |
75
+ | 装饰器参数 | key:常量字符串,必填(字符串需要有引号)。 **注意:** 使用null和undefined作为key时,会隐式转换为对应的字符串,不建议该用法。 |
76
+ | 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。 API Version 12及以上支持Map、Set、Date、undefined和null类型以及这些类型的联合类型,示例见[AppStorage支持联合类型](#appstorage支持联合类型)。 嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现-1)。 **注意:** 变量类型必须被指定,建议和AppStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。 |
77
+ | 不允许装饰的变量类型 | 不支持装饰Function类型。 |
78
+ | 同步类型 | 双向同步:从AppStorage的对应属性到自定义组件,从自定义组件到AppStorage对应属性。 |
79
+ | 被装饰变量的初始值 | 必须本地初始化,如果AppStorage实例中不存在属性,则用该初始值初始化该属性,并存入AppStorage中。 |
80
+ ### 变量的传递/访问规则说明
81
+ | 传递/访问 | 说明 |
82
+ | --- | --- |
83
+ | 从父节点初始化和更新 | 禁止。 |
84
+ | 初始化子节点 | 支持,可用于初始化常规变量、@State、@Link、@Prop、@Provide。 |
85
+ | 是否支持组件外访问 | 否。 |
86
+
87
+ **图2** @StorageLink初始化规则图示
88
+
89
+ ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/b5/v3/2oB-W5JJRju7VMuSz9xowA/zh-cn_image_0000002566707947.png)
90
+ ### 观察变化和行为表现
91
+ **观察变化**
92
+
93
+ - 装饰的数据类型为boolean、string、number时,可以观察到数值变化。
94
+
95
+ - 装饰的数据类型为class或Object时,可以观察到对象整体赋值和属性变化。(详见[从ui内部使用appstorage](#从ui内部使用appstorage))。
96
+
97
+ - 当装饰的对象是数组时,可以观察到数组添加、删除、更新数组单元的变化。详见[装饰Array类型变量](#装饰array类型变量)。
98
+
99
+ - 当装饰的对象是Date时,可以观察到Date整体的赋值,以及通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。详见[装饰Date类型变量](#装饰date类型变量)。
100
+
101
+ - 当装饰的变量是Map时,可以观察到Map整体的赋值,以及通过调用Map的接口set、clear、delete更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。
102
+
103
+ - 当装饰的变量是Set时,可以观察到Set的整体赋值,以及通过调用Set的接口add、clear、delete更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。
104
+
105
+ **框架行为**
106
+
107
+ - 当@StorageLink(key)装饰的数值发生变化时,修改将被同步回AppStorage对应key的属性中。
108
+
109
+ - AppStorage中key对应的数据一旦改变,其绑定的所有的数据(包括双向@StorageLink和单向@StorageProp)都将被同步修改。
110
+
111
+ - @StorageLink(key)装饰的数据是状态变量,其变化不仅会同步到AppStorage,还会触发自定义组件的重新渲染。
112
+ ## 限制条件
113
+ - @StorageProp/@StorageLink的参数必须为string类型,否则编译期会报错。
114
+
115
+ ```ts
116
+ AppStorage.setOrCreate('propA', 47);
117
+
118
+ // 错误写法,编译报错
119
+ @StorageProp() storageProp: number = 1;
120
+ @StorageLink() storageLink: number = 2;
121
+
122
+ // 正确写法
123
+ @StorageProp('propA') storageProp: number = 1;
124
+ @StorageLink('propA') storageLink: number = 2;
125
+ ```
126
+
127
+ - @StorageProp与@StorageLink不支持装饰Function类型的变量,框架会抛出运行时错误。
128
+
129
+ - AppStorage与[PersistentStorage](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-persiststorage)以及[Environment](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-environment)配合使用时,需要注意以下几点:
130
+
131
+ a. 在AppStorage中创建属性后,调用PersistentStorage.[persistProp](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-state-management#persistprop10)接口时,会使用AppStorage中已存在的值,并覆盖PersistentStorage中的同名属性。因此,建议使用相反的调用顺序。反例可见[在PersistentStorage之前访问AppStorage中的属性](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-persiststorage#在persistentstorage之前访问appstorage中的属性)。
132
+
133
+ b. 如果在AppStorage中已创建属性,再调用Environment.[envProp](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-state-management#envprop10)创建同名属性,会调用失败。因为AppStorage已有同名属性,Environment环境变量不会再写入AppStorage中,所以建议不要在AppStorage中使用Environment预置环境变量名。
134
+
135
+ ```ts
136
+ AppStorage.setOrCreate('languageCode', 'en');
137
+ // result结果为false
138
+ let result = Environment.envProp('languageCode','en');
139
+ ```
140
+
141
+ - 状态装饰器装饰的变量,改变会引起UI的渲染更新。如果改变的变量仅用于消息传递,不用于UI更新,推荐使用[emitter](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-emitter)方式。具体示例可见[不建议借助@StorageLink的双向同步机制实现事件通知](#不建议借助storagelink的双向同步机制实现事件通知)。
142
+
143
+ - AppStorage同一进程内共享,UIAbility和UIExtensionAbility是两个进程,所以在UIExtensionAbility中不共享主进程的AppStorage。
144
+ ## 使用场景 ### 从应用逻辑使用AppStorage和LocalStorage
145
+ AppStorage是单例,其所有API均为静态方法,使用方法类似于[LocalStorage](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-localstorage)中对应的非静态方法。
146
+
147
+ ```ts
148
+ AppStorage.setOrCreate('propA', 47);
149
+
150
+ let storage: LocalStorage = new LocalStorage();
151
+ storage.setOrCreate('propA',17);
152
+ let propA: number | undefined = AppStorage.get('propA'); // propA in AppStorage == 47, propA in LocalStorage == 17
153
+ let link1: SubscribedAbstractProperty<number> = AppStorage.link('propA'); // link1.get() == 47
154
+ let link2: SubscribedAbstractProperty<number> = AppStorage.link('propA'); // link2.get() == 47
155
+ let prop: SubscribedAbstractProperty<number> = AppStorage.prop('propA'); // prop.get() == 47
156
+
157
+ link1.set(48); // 双向同步: link1.get() == link2.get() == prop.get() == 48
158
+ prop.set(1); // 单向同步: prop.get() == 1; 但 link1.get() == link2.get() == 48
159
+ link1.set(49); // 双向同步: link1.get() == link2.get() == prop.get() == 49
160
+
161
+ storage.get<number>('propA') // == 17
162
+ storage.set('propA', 101);
163
+ storage.get<number>('propA') // == 101
164
+
165
+ AppStorage.get<number>('propA') // == 49
166
+ link1.get() // == 49
167
+ link2.get() // == 49
168
+ prop.get() // == 49
169
+ ```
170
+ ### 从UI内部使用AppStorage
171
+ @StorageLink与AppStorage配合使用,通过AppStorage中的属性创建双向数据同步。
172
+
173
+ @StorageProp与AppStorage配合使用,通过AppStorage中的属性创建单向数据同步。
174
+
175
+ ```TypeScript
176
+ import { hilog } from '@kit.PerformanceAnalysisKit';
177
+ const DOMAIN = 0x0001;
178
+ const TAG: string = '[SampleAppStorage]';
179
+
180
+ class Data {
181
+ public code: number;
182
+
183
+ constructor(code: number) {
184
+ this.code = code;
185
+ }
186
+ }
187
+
188
+ AppStorage.setOrCreate('propA', 47);
189
+ AppStorage.setOrCreate('propB', new Data(50));
190
+ let storage = new LocalStorage();
191
+ storage.setOrCreate('linkA', 48);
192
+ storage.setOrCreate('linkB', new Data(100));
193
+
194
+ @Entry(storage)
195
+ @Component
196
+ struct TestStorageProp {
197
+ @StorageLink('propA') storageLink: number = 1;
198
+ @StorageProp('propA') storageProp: number = 1;
199
+ @StorageLink('propB') storageLinkObject: Data = new Data(1);
200
+ @StorageProp('propB') storagePropObject: Data = new Data(1);
201
+
202
+ build() {
203
+ Column({ space: 20 }) {
204
+ // @StorageLink与AppStorage建立双向联系,更改数据会同步回AppStorage中key为'propA'的值
205
+ Text(`storageLink ${this.storageLink}`)
206
+ .onClick(() => {
207
+ this.storageLink += 1;
208
+ })
209
+
210
+ // @StorageProp与AppStorage建立单向联系,更改数据不会同步回AppStorage中key为'propA'的值
211
+ // 但能被AppStorage的set/setorCreate更新值
212
+ Text(`storageProp ${this.storageProp}`)
213
+ .onClick(() => {
214
+ this.storageProp += 1;
215
+ })
216
+
217
+ // AppStorage的API虽然能获取值,但是不具有刷新UI的能力,日志能看到数值更改
218
+ // 依赖@StorageLink/@StorageProp才能建立起与自定义组件的联系,刷新UI
219
+ Text(`change by AppStorage: ${AppStorage.get<number>('propA')}`)
220
+ .onClick(() => {
221
+ hilog.info(DOMAIN, TAG, `Appstorage.get: ${AppStorage.get<number>('propA')}`);
222
+ AppStorage.set<number>('propA', 100);
223
+ })
224
+
225
+ Text(`storageLinkObject ${this.storageLinkObject.code}`)
226
+ .onClick(() => {
227
+ this.storageLinkObject.code += 1;
228
+ })
229
+
230
+ Text(`storagePropObject ${this.storagePropObject.code}`)
231
+ .onClick(() => {
232
+ this.storagePropObject.code += 1;
233
+ })
234
+ }
235
+ }
236
+ }
237
+ ```
238
+ ### AppStorage支持联合类型
239
+ 在下面的示例中,变量linkA的类型为number | null,变量linkB的类型为number | undefined。Text组件初始化分别显示为null和undefined,点击切换为数字,再次点击切换回null和undefined。
240
+
241
+ ```TypeScript
242
+ @Component
243
+ struct StorageLinkComponent {
244
+ @StorageLink('linkA') linkA: number | null = null;
245
+ @StorageLink('linkB') linkB: number | undefined = undefined;
246
+
247
+ build() {
248
+ Column() {
249
+ Text('@StorageLink接口初始化,@StorageLink取值')
250
+ Text(`${this.linkA}`).fontSize(20).onClick(() => {
251
+ this.linkA ? this.linkA = null : this.linkA = 1;
252
+ })
253
+ Text(`${this.linkB}`).fontSize(20).onClick(() => {
254
+ this.linkB ? this.linkB = undefined : this.linkB = 1;
255
+ })
256
+ }
257
+ .borderWidth(3).borderColor(Color.Red)
258
+ }
259
+ }
260
+
261
+ @Component
262
+ struct StoragePropComponent {
263
+ @StorageProp('propA') propA: number | null = null;
264
+ @StorageProp('propB') propB: number | undefined = undefined;
265
+
266
+ build() {
267
+ Column() {
268
+ Text('@StorageProp接口初始化,@StorageProp取值')
269
+ Text(`${this.propA}`).fontSize(20).onClick(() => {
270
+ this.propA ? this.propA = null : this.propA = 1;
271
+ })
272
+ Text(`${this.propB}`).fontSize(20).onClick(() => {
273
+ this.propB ? this.propB = undefined : this.propB = 1;
274
+ })
275
+ }
276
+ .borderWidth(3).borderColor(Color.Blue)
277
+ }
278
+ }
279
+
280
+ @Entry
281
+ @Component
282
+ struct TestPageStorageLink {
283
+ build() {
284
+ Row() {
285
+ Column() {
286
+ StorageLinkComponent()
287
+ StoragePropComponent()
288
+ }
289
+ .width('100%')
290
+ }
291
+ .height('100%')
292
+ }
293
+ }
294
+ ```
295
+ ### 装饰Array类型变量
296
+ 在下面的示例中,@StorageLink装饰的message类型为number[],点击Button改变message的值,视图会随之刷新。
297
+
298
+ ```TypeScript
299
+ @Entry
300
+ @Component
301
+ struct ArraySample {
302
+ @StorageLink('array') message: number[] = [0, 1, 2, 3];
303
+
304
+ build() {
305
+ Column() {
306
+ ForEach(this.message, (item: number) => {
307
+ Text(`${item}`)
308
+ .fontSize(20)
309
+ .margin(10)
310
+ })
311
+ // 新增数组元素,触发UI刷新
312
+ Button('Push element')
313
+ .onClick(() => {
314
+ this.message.push(4);
315
+ })
316
+ .width(300)
317
+ .margin(10)
318
+ // 删除数组元素,触发UI刷新
319
+ Button('Pop element')
320
+ .onClick(() => {
321
+ this.message.pop();
322
+ })
323
+ .width(300)
324
+ .margin(10)
325
+ // 对数组整体重新赋值,触发UI刷新
326
+ Button('Reset array')
327
+ .onClick(() => {
328
+ this.message = [9, 8, 7, 6];
329
+ })
330
+ .width(300)
331
+ .margin(10)
332
+ // 更新数组元素,触发UI刷新
333
+ Button('Modify element[0]')
334
+ .onClick(() => {
335
+ this.message[0] = 10;
336
+ })
337
+ .width(300)
338
+ .margin(10)
339
+ }
340
+ }
341
+ }
342
+ ```
343
+ ### 装饰Date类型变量
344
+ > **说明:**
345
+ >
346
+ > 从API version 12开始,AppStorage支持Date类型。
347
+ >
348
+
349
+ 在下面的示例中,@StorageLink装饰的selectedDate类型为Date。点击Button改变selectedDate的值,视图会随之刷新。
350
+
351
+ ```TypeScript
352
+ @Entry
353
+ @Component
354
+ struct DateSample {
355
+ @StorageLink('date') selectedDate: Date = new Date('2021-08-08');
356
+
357
+ build() {
358
+ Column() {
359
+ Button('set selectedDate to 2023-07-08')
360
+ .margin(10)
361
+ .onClick(() => {
362
+ AppStorage.setOrCreate('date', new Date('2023-07-08'));
363
+ })
364
+ Button('increase the year by 1')
365
+ .margin(10)
366
+ .onClick(() => {
367
+ this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1);
368
+ })
369
+ Button('increase the month by 1')
370
+ .margin(10)
371
+ .onClick(() => {
372
+ this.selectedDate.setMonth(this.selectedDate.getMonth() + 1);
373
+ })
374
+ Button('increase the day by 1')
375
+ .margin(10)
376
+ .onClick(() => {
377
+ this.selectedDate.setDate(this.selectedDate.getDate() + 1);
378
+ })
379
+ DatePicker({
380
+ start: new Date('1970-1-1'),
381
+ end: new Date('2100-1-1'),
382
+ selected: $$this.selectedDate
383
+ })
384
+ }.width('100%')
385
+ }
386
+ }
387
+ ```
388
+ ### 装饰Map类型变量
389
+ > **说明:**
390
+ >
391
+ > 从API version 12开始,AppStorage支持Map类型。
392
+ >
393
+
394
+ 在下面的示例中,@StorageLink装饰的message类型为Map<number, string>,点击Button改变message的值,视图会随之刷新。
395
+
396
+ ```TypeScript
397
+ @Entry
398
+ @Component
399
+ struct MapSample {
400
+ @StorageLink('map') message: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
401
+
402
+ build() {
403
+ Row() {
404
+ Column() {
405
+ ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
406
+ Text(`${item[0]}`).fontSize(30)
407
+ Text(`${item[1]}`).fontSize(30)
408
+ Divider()
409
+ })
410
+ Button('init map').onClick(() => {
411
+ this.message = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
412
+ })
413
+ Button('set new one').onClick(() => {
414
+ this.message.set(4, 'd');
415
+ })
416
+ Button('clear').onClick(() => {
417
+ this.message.clear();
418
+ })
419
+ Button('replace the existing one').onClick(() => {
420
+ this.message.set(0, 'aa');
421
+ })
422
+ Button('delete the existing one').onClick(() => {
423
+ AppStorage.get<Map<number, string>>('map')?.delete(0);
424
+ })
425
+ }
426
+ .width('100%')
427
+ }
428
+ .height('100%')
429
+ }
430
+ }
431
+ ```
432
+ ### 装饰Set类型变量
433
+ > **说明:**
434
+ >
435
+ > 从API version 12开始,AppStorage支持Set类型。
436
+ >
437
+
438
+ 在下面的示例中,@StorageLink装饰的memberSet类型为Set<number>,点击Button改变memberSet的值,视图会随之刷新。
439
+
440
+ ```TypeScript
441
+ @Entry
442
+ @Component
443
+ struct SetSample {
444
+ @StorageLink('set') memberSet: Set<number> = new Set([0, 1, 2, 3, 4]);
445
+
446
+ build() {
447
+ Row() {
448
+ Column() {
449
+ ForEach(Array.from(this.memberSet.entries()), (item: [number, number]) => {
450
+ Text(`${item[0]}`)
451
+ .fontSize(30)
452
+ Divider()
453
+ })
454
+ Button('init set')
455
+ .onClick(() => {
456
+ this.memberSet = new Set([0, 1, 2, 3, 4]);
457
+ })
458
+ Button('set new one')
459
+ .onClick(() => {
460
+ AppStorage.get<Set<number>>('set')?.add(5);
461
+ })
462
+ Button('clear')
463
+ .onClick(() => {
464
+ this.memberSet.clear();
465
+ })
466
+ Button('delete the first one')
467
+ .onClick(() => {
468
+ this.memberSet.delete(0);
469
+ })
470
+ }
471
+ .width('100%')
472
+ }
473
+ .height('100%')
474
+ }
475
+ }
476
+ ```
477
+ ### AppStorage在多页面中共享使用
478
+ 在下面示例中,Index和Page页面通过同一个全局AppStorage对象共享linkA数据。在一处修改其值,另一处也能获取到更新后的值。
479
+
480
+ ```TypeScript
481
+ AppStorage.setOrCreate('linkA', 47)
482
+ AppStorage.setOrCreate('propB', 48)
483
+
484
+ @Entry
485
+ @Component
486
+ struct Index {
487
+ @StorageLink('linkA') linkA: number = 1; // 与AppStorage进行双向数据同步
488
+ @StorageProp('propB') propB: number = 1; // 与AppStorage进行单向数据同步
489
+ pageStack: NavPathStack = new NavPathStack();
490
+
491
+ build() {
492
+ Navigation(this.pageStack) {
493
+ Row() {
494
+ Column({ space: 5 }) {
495
+ Text(`${this.linkA}`)
496
+ .fontSize(50)
497
+ .fontWeight(FontWeight.Bold)
498
+ Text(`${this.propB}`)
499
+ .fontSize(50)
500
+ .fontWeight(FontWeight.Bold)
501
+ Button('Change linkA')
502
+ .onClick(() => {
503
+ // 刷新UI,修改将会被同步回AppStorage
504
+ this.linkA++;
505
+ })
506
+ Button('Change propB')
507
+ .onClick(() => {
508
+ // 刷新UI,修改不会被同步回AppStorage
509
+ this.propB++;
510
+ })
511
+ Button('To Page')
512
+ .onClick(() => {
513
+ this.pageStack.pushPathByName('Page', null);
514
+ })
515
+ }
516
+ .width('100%')
517
+ }
518
+ .height('100%')
519
+ }
520
+ }
521
+ }
522
+ ```
523
+
524
+ ```TypeScript
525
+ @Builder
526
+ export function PageBuilder() {
527
+ Page()
528
+ }
529
+
530
+ // 应用全局共享一个AppStorage
531
+ @Component
532
+ struct Page {
533
+ @StorageLink('linkA') linkA: number = 2; // 与AppStorage进行双向数据同步
534
+ @StorageProp('propB') propB: number = 2; // 与AppStorage进行单向数据同步
535
+ pageStack: NavPathStack = new NavPathStack();
536
+
537
+ build() {
538
+ NavDestination() {
539
+ Row() {
540
+ Column({ space: 5 }) {
541
+ Text(`${this.linkA}`)
542
+ .fontSize(50)
543
+ .fontWeight(FontWeight.Bold)
544
+ Text(`${this.propB}`)
545
+ .fontSize(50)
546
+ .fontWeight(FontWeight.Bold)
547
+ Button('Change linkA')
548
+ .onClick(() => {
549
+ // 刷新UI,修改将会被同步回AppStorage
550
+ this.linkA++;
551
+ })
552
+ Button('Change propB')
553
+ .onClick(() => {
554
+ // 刷新UI,修改不会被同步回AppStorage
555
+ this.propB++;
556
+ })
557
+ Button('Back Index')
558
+ .onClick(() => {
559
+ this.pageStack.pop();
560
+ })
561
+ }
562
+ .width('100%')
563
+ }
564
+ }
565
+ .onReady((context: NavDestinationContext) => {
566
+ this.pageStack = context.pathStack;
567
+ })
568
+ }
569
+ }
570
+ ```
571
+
572
+ 使用Navigation时,需要手动添加系统路由表文件src/main/resources/base/profile/router_map.json,并在module.json5中添加:"routerMap": "$profile:router_map"。
573
+
574
+ ```json
575
+ {
576
+ "routerMap": [
577
+ {
578
+ "name": "Page",
579
+ "pageSourceFile": "src/main/ets/pages/Page.ets",
580
+ "buildFunction": "PageBuilder",
581
+ "data": {
582
+ "description": "AppStorage example"
583
+ }
584
+ }
585
+ ]
586
+ }
587
+ ```
588
+ ## AppStorage使用建议 ### 不建议借助@StorageLink的双向同步机制实现事件通知
589
+ 不建议使用@StorageLink和AppStorage的双向同步机制来实现事件通知。AppStorage中的变量可能绑定在多个页面的组件中,但事件通知不一定需要通知到所有这些组件。此外,当这些@StorageLink装饰的变量在UI中使用时,会触发UI刷新,造成不必要的性能影响。
590
+
591
+ 示例代码中,TapImage中的点击事件会触发AppStorage中tapIndex对应属性的改变。由于@StorageLink是双向同步的,修改会同步回AppStorage中,因此所有绑定AppStorage的tapIndex自定义组件都能感知到tapIndex的变化。使用@Watch监听到tapIndex的变化后,修改状态变量tapColor,从而触发UI刷新(此处tapIndex未直接绑定在UI上,因此tapIndex的变化不会直接触发UI刷新)。
592
+
593
+ 使用该机制实现事件通知时,应确保AppStorage中的变量不直接被绑定到UI上,同时控制[@Watch](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-watch)函数的复杂度。如果@Watch函数执行时间过长,会影响UI刷新效率。
594
+
595
+ ```TypeScript
596
+ import { hilog } from '@kit.PerformanceAnalysisKit';
597
+ const DOMAIN = 0x0001;
598
+ const TAG: string = '[SampleAppStorage]';
599
+
600
+ class ViewData {
601
+ public title: string;
602
+ public uri: Resource;
603
+ public color: Color = Color.Black;
604
+
605
+ constructor(title: string, uri: Resource) {
606
+ this.title = title;
607
+ this.uri = uri;
608
+ }
609
+ }
610
+
611
+ @Entry
612
+ @Component
613
+ struct Gallery {
614
+ // 请将$r('app.media.startIcon')替换为实际资源文件
615
+ dataList: Array<ViewData> =
616
+ [new ViewData('flower', $r('app.media.startIcon')), new ViewData('OMG', $r('app.media.startIcon')),
617
+ new ViewData('OMG', $r('app.media.startIcon'))];
618
+ scroller: Scroller = new Scroller();
619
+
620
+ build() {
621
+ Column() {
622
+ Grid(this.scroller) {
623
+ ForEach(this.dataList, (item: ViewData, index?: number) => {
624
+ GridItem() {
625
+ TapImage({
626
+ uri: item.uri,
627
+ index: index
628
+ })
629
+ }.aspectRatio(1)
630
+
631
+ }, (item: ViewData, index?: number) => {
632
+ return JSON.stringify(item) + index;
633
+ })
634
+ }.columnsTemplate('1fr 1fr')
635
+ }
636
+
637
+ }
638
+ }
639
+
640
+ @Component
641
+ export struct TapImage {
642
+ @StorageLink('tapIndex') @Watch('onTapIndexChange') tapIndex: number = -1;
643
+ @State tapColor: Color = Color.Black;
644
+ private index: number = 0;
645
+ private uri: Resource = {
646
+ id: 0,
647
+ type: 0,
648
+ moduleName: '',
649
+ bundleName: ''
650
+ };
651
+
652
+ // 判断是否被选中
653
+ onTapIndexChange() {
654
+ if (this.tapIndex >= 0 && this.index === this.tapIndex) {
655
+ hilog.info(DOMAIN, TAG, `tapindex: ${this.tapIndex}, index: ${this.index}, red`);
656
+ this.tapColor = Color.Red;
657
+ } else {
658
+ hilog.info(DOMAIN, TAG, `tapindex: ${this.tapIndex}, index: ${this.index}, black`);
659
+ this.tapColor = Color.Black;
660
+ }
661
+ }
662
+
663
+ build() {
664
+ Column() {
665
+ Image(this.uri)
666
+ .objectFit(ImageFit.Cover)
667
+ .onClick(() => {
668
+ this.tapIndex = this.index;
669
+ })
670
+ .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor })
671
+ }
672
+
673
+ }
674
+ }
675
+ ```
676
+
677
+ 相比借助@StorageLink的双向同步机制实现事件通知,开发者可以使用emit订阅某个事件并接收事件回调的方式来减少开销,增强代码的可读性。
678
+
679
+ > **说明:**
680
+ >
681
+ > emit接口不支持在Previewer预览器中使用。
682
+ >
683
+
684
+ ```TypeScript
685
+ import { emitter } from '@kit.BasicServicesKit';
686
+ import { hilog } from '@kit.PerformanceAnalysisKit';
687
+ const DOMAIN = 0x0001;
688
+ const TAG: string = '[SampleAppStorage]';
689
+
690
+ let nextId: number = 0;
691
+
692
+ class ViewData {
693
+ public title: string;
694
+ public uri: Resource;
695
+ public color: Color = Color.Black;
696
+ public id: number;
697
+
698
+ constructor(title: string, uri: Resource) {
699
+ this.title = title;
700
+ this.uri = uri;
701
+ this.id = nextId++;
702
+ }
703
+ }
704
+
705
+ @Entry
706
+ @Component
707
+ struct Gallery {
708
+ // 请将$r('app.media.startIcon')替换为实际资源文件
709
+ dataList: Array<ViewData> =
710
+ [new ViewData('flower', $r('app.media.startIcon')), new ViewData('OMG', $r('app.media.startIcon')),
711
+ new ViewData('OMG', $r('app.media.startIcon'))];
712
+ scroller: Scroller = new Scroller();
713
+ private preIndex: number = -1;
714
+
715
+ build() {
716
+ Column() {
717
+ Grid(this.scroller) {
718
+ ForEach(this.dataList, (item: ViewData) => {
719
+ GridItem() {
720
+ TapImage({
721
+ uri: item.uri,
722
+ index: item.id
723
+ })
724
+ }.aspectRatio(1)
725
+ .onClick(() => {
726
+ if (this.preIndex === item.id) {
727
+ return;
728
+ }
729
+ let innerEvent: emitter.InnerEvent = { eventId: item.id };
730
+ // 选中态:黑变红
731
+ let eventData: emitter.EventData = {
732
+ data: {
733
+ 'colorTag': 1
734
+ }
735
+ };
736
+ emitter.emit(innerEvent, eventData);
737
+
738
+ if (this.preIndex != -1) {
739
+ hilog.info(DOMAIN, TAG, `preIndex: ${this.preIndex}, index: ${item.id}, black`);
740
+ let innerEvent: emitter.InnerEvent = { eventId: this.preIndex };
741
+ // 取消选中态:红变黑
742
+ let eventData: emitter.EventData = {
743
+ data: {
744
+ 'colorTag': 0
745
+ }
746
+ };
747
+ emitter.emit(innerEvent, eventData);
748
+ }
749
+ this.preIndex = item.id;
750
+ })
751
+ }, (item: ViewData) => JSON.stringify(item))
752
+ }.columnsTemplate('1fr 1fr')
753
+ }
754
+
755
+ }
756
+ }
757
+
758
+ @Component
759
+ export struct TapImage {
760
+ @State tapColor: Color = Color.Black;
761
+ private index: number = 0;
762
+ private uri: Resource = {
763
+ id: 0,
764
+ type: 0,
765
+ moduleName: '',
766
+ bundleName: ''
767
+ };
768
+
769
+ onTapIndexChange(colorTag: emitter.EventData) {
770
+ if (colorTag.data != null) {
771
+ this.tapColor = colorTag.data.colorTag ? Color.Red : Color.Black;
772
+ }
773
+ }
774
+
775
+ aboutToAppear() {
776
+ // 定义事件ID
777
+ let innerEvent: emitter.InnerEvent = { eventId: this.index };
778
+ emitter.on(innerEvent, data => {
779
+ this.onTapIndexChange(data);
780
+ });
781
+ }
782
+
783
+ build() {
784
+ Column() {
785
+ Image(this.uri)
786
+ .objectFit(ImageFit.Cover)
787
+ .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor })
788
+ }
789
+ }
790
+ }
791
+ ```
792
+
793
+ 以上通知事件逻辑简单,也可以简化成三元表达式。
794
+
795
+ ```TypeScript
796
+ class ViewData {
797
+ public title: string;
798
+ public uri: Resource;
799
+ public color: Color = Color.Black;
800
+
801
+ constructor(title: string, uri: Resource) {
802
+ this.title = title;
803
+ this.uri = uri;
804
+ }
805
+ }
806
+
807
+ @Entry
808
+ @Component
809
+ struct Gallery {
810
+ // 请将$r('app.media.startIcon')替换为实际资源文件
811
+ dataList: Array<ViewData> =
812
+ [new ViewData('flower', $r('app.media.startIcon')), new ViewData('OMG', $r('app.media.startIcon')),
813
+ new ViewData('OMG', $r('app.media.startIcon'))];
814
+ scroller: Scroller = new Scroller();
815
+
816
+ build() {
817
+ Column() {
818
+ Grid(this.scroller) {
819
+ ForEach(this.dataList, (item: ViewData, index?: number) => {
820
+ GridItem() {
821
+ TapImage({
822
+ uri: item.uri,
823
+ index: index
824
+ })
825
+ }.aspectRatio(1)
826
+
827
+ }, (item: ViewData, index?: number) => {
828
+ return JSON.stringify(item) + index;
829
+ })
830
+ }.columnsTemplate('1fr 1fr')
831
+ }
832
+
833
+ }
834
+ }
835
+
836
+ @Component
837
+ export struct TapImage {
838
+ @StorageLink('tapIndex') tapIndex: number = -1;
839
+ private index: number = 0;
840
+ private uri: Resource = {
841
+ id: 0,
842
+ type: 0,
843
+ moduleName: '',
844
+ bundleName: ''
845
+ };
846
+
847
+ build() {
848
+ Column() {
849
+ Image(this.uri)
850
+ .objectFit(ImageFit.Cover)
851
+ .onClick(() => {
852
+ this.tapIndex = this.index;
853
+ })
854
+ .border({
855
+ width: 5,
856
+ style: BorderStyle.Dotted,
857
+ color: (this.tapIndex >= 0 && this.index === this.tapIndex) ? Color.Red : Color.Black
858
+ })
859
+ }
860
+ }
861
+ }
862
+ ```
863
+ ### @StorageProp和AppStorage接口配合使用时,需要注意更新规则
864
+ 使用setOrCreate/set接口更新key的值时,如果值相同,setOrCreate不会通知@StorageLink/@StorageProp更新,但因为@StorageProp本身有数据副本,更改值不会同步给AppStorage,这会导致开发者误认已通过AppStorage改了值,但实际上未通知@StorageProp更新值的情况。示例如下。
865
+
866
+ ```TypeScript
867
+ import { hilog } from '@kit.PerformanceAnalysisKit';
868
+ const DOMAIN = 0x0001;
869
+ const TAG: string = '[SampleAppStorage]';
870
+ AppStorage.setOrCreate('propA', false);
871
+
872
+ @Entry
873
+ @Component
874
+ struct PageStorageProp {
875
+ @StorageProp('propA') @Watch('onChange') propA: boolean = false;
876
+
877
+ onChange() {
878
+ hilog.info(DOMAIN, TAG, `propA change`);
879
+ }
880
+
881
+ aboutToAppear(): void {
882
+ this.propA = true;
883
+ }
884
+
885
+ build() {
886
+ Column() {
887
+ Text(`${this.propA}`)
888
+ Button('change')
889
+ .onClick(() => {
890
+ AppStorage.setOrCreate('propA', false);
891
+ hilog.info(DOMAIN, TAG, `propA: ${this.propA}`);
892
+ })
893
+ }
894
+ }
895
+ }
896
+ ```
897
+
898
+ 上述示例,在点击事件之前,propA的值已经在本地被更改为true,而AppStorage中存的值仍为false。当点击事件通过setOrCreate接口尝试更新propA的值为false时,由于AppStorage中的值为false,两者相等,不会触发更新同步,因此@StorageProp的值仍为true。
899
+
900
+ 实现二者同步有以下两种方式:
901
+
902
+ - 将@StorageProp更改为@StorageLink。
903
+ - 本地更改值的方式变为使用AppStorage.setOrCreate('propA', true)的方式。