@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,2089 @@
1
+ # @Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
2
+ 上文所述的装饰器(包括[@State](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state)、[@Prop](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-prop)、[@Link](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-link)、[@Provide和@Consume](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-provide-and-consume)装饰器)仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组、对象数组、嵌套类场景,无法观察到第二层的属性变化。因此,为了实现对嵌套数据结构中深层属性变化的观察,引入了@Observed和@ObjectLink装饰器。
3
+
4
+ @Observed/@ObjectLink适用于观察嵌套对象(对象的属性是对象)属性的变化,需要开发者对装饰器的基本观察能力有一定的了解,再来对比阅读该文档。建议提前阅读:[@State](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state)的基本用法。最佳实践请参考[状态管理最佳实践](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-status-management)。常见问题请参考[状态管理常见问题](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management-faq)。
5
+
6
+ > **说明:**
7
+ >
8
+ > 从API version 9开始,这两个装饰器支持在ArkTS卡片中使用。 从API version 11开始,这两个装饰器支持在元服务中使用。
9
+ >
10
+ ## 概述
11
+ @ObjectLink和@Observed类装饰器配合使用,可实现嵌套对象或数组的双向数据同步,使用方式如下:
12
+
13
+ - 将数组项或类属性声明为@Observed装饰的类型,示例请参考[嵌套对象](#嵌套对象)。
14
+
15
+ - 在子组件中使用@ObjectLink装饰的状态变量,用于接收父组件@Observed装饰的类实例,从而建立双向数据绑定。
16
+
17
+ - API version 19之前,@ObjectLink只能接收@Observed装饰的类实例;API version 19及以后,@ObjectLink也可以接收复杂类型,无@Observed装饰的限制。但需注意,如需观察嵌套类型场景,需要其接收@Observed装饰的类实例或[makeV1Observed](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-statemanagement#makev1observed19)的返回值。示例请参考[二维数组](#二维数组)。
18
+
19
+ 开发者如需实现单向数据同步,需要搭配@Prop使用,示例请参考[@Prop与@ObjectLink的差异](#prop与objectlink的差异)。
20
+ ## 装饰器说明
21
+ | @Observed类装饰器 | 说明 |
22
+ | --- | --- |
23
+ | 装饰器参数 | 无。 |
24
+ | 类装饰器 | 装饰class。需要放在class的定义前,使用new创建类对象。 |
25
+
26
+ | @ObjectLink变量装饰器 | 说明 |
27
+ | --- | --- |
28
+ | 装饰器参数 | 无。 |
29
+ | 允许装饰的变量类型 | 支持继承Date、[Array](#二维数组)的class实例。 API version 11及以后支持继承[Map](#继承map类)、[Set](#继承set类)的class实例以及@Observed装饰类和undefined或null组成的联合类型,比如ClassA | ClassB、 ClassA | undefined 或者 ClassA | null, 示例请参考[@ObjectLink支持联合类型](#objectlink支持联合类型)。 API version 19之前,必须为被@Observed装饰的class实例。 API version 19及以后,@ObjectLink可以被复杂类型初始化,即class、object或built-in类型。但当观察嵌套类型时,仍需其接收@Observed装饰的类实例或makeV1Observed的返回值。 **说明:** @ObjectLink不支持简单类型,如果开发者需要使用简单类型,可以使用[@Prop](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-prop)。 |
30
+ | 被装饰变量的初始值 | 禁止本地初始化。 |
31
+
32
+ @ObjectLink的属性可以被改变,但不允许整体赋值,即@ObjectLink装饰的变量是只读的。
33
+
34
+ ```ts
35
+ // 允许@ObjectLink装饰的数据属性赋值
36
+ this.objLink.a= ...
37
+ // 不允许@ObjectLink装饰的数据自身赋值
38
+ this.objLink= ...
39
+ ```
40
+
41
+ > **说明:**
42
+ >
43
+ > @ObjectLink装饰的变量不能被赋值,如果要使用赋值操作,请使用[@Prop](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-prop)。 @Prop装饰的变量和数据源的关系是单向同步,@Prop装饰的变量在本地拷贝了数据源,所以它允许本地更改,如果父组件中的数据源有更新,@Prop装饰的变量在本地的修改将被覆盖。 @ObjectLink装饰的变量和数据源的关系是双向同步,@ObjectLink装饰的变量相当于指向数据源的指针。禁止对@ObjectLink装饰的变量赋值,如果发生@ObjectLink装饰的变量的赋值,则同步链将被打断。
44
+ >
45
+ ## 变量的传递/访问规则说明
46
+ | @ObjectLink传递/访问 | 说明 |
47
+ | --- | --- |
48
+ | 从父组件初始化 | 必须指定。 必须使用复杂类型初始化@ObjectLink装饰的变量,如果需要观察变化需要满足以下场景: - API version 19之前,类型必须为被@Observed装饰的class实例。 - API version 19及以后,@ObjectLink可以被复杂类型初始化,即class、object或built-in类型。但当观察嵌套类型时,仍需其接收@Observed装饰的类实例或makeV1Observed的返回值。 - 同步源的class或者数组必须是[@State](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state),[@Link](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-link),[@Provide](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-provide-and-consume),[@Consume](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-provide-and-consume)或者@ObjectLink装饰的数据。 同步源是数组项的示例请参考[对象数组](#对象数组)。初始化的class的示例请参考[嵌套对象](#嵌套对象)。 |
49
+ | 与源对象同步 | 双向。 |
50
+ | 可以初始化子组件 | 允许,可用于初始化常规变量、@State、@Link、@Prop、@Provide |
51
+
52
+ **图1** 初始化规则图示
53
+
54
+ ![]((https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/cc/v3/Xuu3xnsQQxaIUchsXGAEoQ/zh-cn_image_0000002566707939.png)
55
+ ## 观察变化和行为表现 ### 观察变化
56
+ API version 19之前,@Observed装饰的类,如果其属性为非简单类型,如class、Object、Array、Map、Set和Date,那么这些属性也需要被@Observed装饰,否则将观察不到这些属性的变化或内置类型的API调用。API version 19及以后,也可以通过使用[makeV1Observed](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-statemanagement#makev1observed19)来观察嵌套类属性的变化。
57
+
58
+ ```TypeScript
59
+ class Child {
60
+ public num: number;
61
+
62
+ constructor(num: number) {
63
+ this.num = num;
64
+ }
65
+ }
66
+
67
+ @Observed
68
+ class Parent {
69
+ public child: Child;
70
+ public count: number;
71
+
72
+ constructor(child: Child, count: number) {
73
+ this.child = child;
74
+ this.count = count;
75
+ }
76
+ }
77
+ ```
78
+
79
+ 以上示例中,Parent被@Observed装饰,其成员变量的赋值的变化是可以被观察到的,但对于Child,没有被@Observed装饰,其属性的修改不能被观察到。若想观察Child的属性修改变化,示例请参考[嵌套对象](#嵌套对象)。
80
+
81
+ ```TypeScript
82
+ @ObjectLink parent: Parent;
83
+
84
+ build() {
85
+ Column() {
86
+ Button('click me')
87
+ .onClick(() => {
88
+ // 赋值变化可以被观察到
89
+ this.parent.child = new Child(5);
90
+ this.parent.count = 5;
91
+ // Child没有被@Observed装饰,其属性的变化观察不到
92
+ this.parent.child.num = 5;
93
+ // ···
94
+ })
95
+ }
96
+ }
97
+ ```
98
+
99
+ @ObjectLink接收对象时,如果对象被@State或其他状态变量装饰器装饰,则可以观察第一层变化。示例请参考[对象类型](#对象类型)。
100
+
101
+ @ObjectLink接收嵌套对象时,内层对象需要为被@Observed装饰的class类型。从API version 19开始,内层对象也支持被[makeV1Observed](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-statemanagement#makev1observed19)处理的返回值。示例请参考[嵌套对象](#嵌套对象)。
102
+
103
+ @ObjectLink推荐设计单独的自定义组件来渲染每一个数组或对象。此时,对象数组或嵌套对象需要两个自定义组件,一个自定义组件呈现外部数组/对象,另一个自定义组件呈现嵌套在数组/对象内的类对象。可以观察到:
104
+
105
+ - 其属性的数值的变化,其中属性是指Object.keys(observedObject)返回的所有属性,示例请参考[嵌套对象](#嵌套对象)。
106
+
107
+ - 如果数据源是数组,则可以观察到数组项的替换,如果数据源是class,可观察到class的属性的变化,示例请参考[对象数组](#对象数组)。
108
+
109
+ @ObjectLink装饰继承于Date的class时,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。
110
+
111
+ ```TypeScript
112
+ @Observed
113
+ class DateClass extends Date {
114
+ constructor(args: number | string) {
115
+ super(args);
116
+ }
117
+ }
118
+
119
+ @Observed
120
+ class NewDate {
121
+ public data: DateClass;
122
+
123
+ constructor(data: DateClass) {
124
+ this.data = data;
125
+ }
126
+ }
127
+
128
+ @Component
129
+ struct Child {
130
+ label: string = 'date';
131
+ @ObjectLink data: DateClass;
132
+
133
+ build() {
134
+ Column() {
135
+ Button('child increase the day by 1')
136
+ .onClick(() => {
137
+ this.data.setDate(this.data.getDate() + 1);
138
+ })
139
+ DatePicker({
140
+ start: new Date('1970-1-1'),
141
+ end: new Date('2100-1-1'),
142
+ selected: this.data
143
+ })
144
+ }
145
+ }
146
+ }
147
+
148
+ @Entry
149
+ @Component
150
+ struct Parent {
151
+ @State newData: NewDate = new NewDate(new DateClass('2023-1-1'));
152
+
153
+ build() {
154
+ Column() {
155
+ Child({ label: 'date', data: this.newData.data })
156
+
157
+ Button('parent update the new date')
158
+ .onClick(() => {
159
+ this.newData.data = new DateClass('2023-07-07');
160
+ })
161
+ Button(`ViewB: this.newData = new NewDate(new DateClass('2023-08-20'))`)
162
+ .onClick(() => {
163
+ this.newData = new NewDate(new DateClass('2023-08-20'));
164
+ })
165
+ }
166
+ }
167
+ }
168
+ ```
169
+
170
+ @ObjectLink装饰继承于Map的class时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。示例请参考[继承Map类](#继承map类)。
171
+
172
+ @ObjectLink装饰继承于Set的class时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。示例请参考[继承Set类](#继承set类)。
173
+ ### 框架行为
174
+ - 初始渲染:
175
+
176
+ a. @Observed装饰的class的实例会被代理对象包装,代理了class上的属性的setter和getter方法。
177
+
178
+ b. 子组件中@ObjectLink装饰的变量从父组件初始化,接收被@Observed装饰的class的实例,@ObjectLink的包装类会将自己注册给@Observed class。这里的注册行为指的是,@ObjectLink包装类会向@Observed实例提供自身的引用,让@Observed实例将其添加到依赖列表中,以便属性变化时能通知到它。
179
+
180
+ - 属性更新:当@Observed装饰的class属性改变时,会执行代理的setter和getter,然后遍历依赖它的@ObjectLink包装类,通知数据更新。
181
+ ## 限制条件
182
+ - 使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。
183
+
184
+ - @ObjectLink装饰器不建议在[@Entry](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-create-custom-components#entry)装饰的自定义组件中使用,编译时会产生告警。
185
+
186
+ - @ObjectLink装饰的类型必须是复杂类型,否则会有编译时报错。
187
+
188
+ - API version 19前,@ObjectLink装饰的变量类型必须是显式地由@Observed装饰的类。如果未指定类型,或不是@Observed装饰的class,编译时报错。
189
+
190
+ API version 19及以后,@ObjectLink也可以被[makeV1Observed](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-statemanagement#makev1observed19)的返回值初始化,若@ObjectLink接收未使用@Observed装饰的class或makeV1Observed返回值进行初始化,则会有运行时告警日志。
191
+
192
+ ```ts
193
+ class Test {
194
+ msg: number;
195
+
196
+ constructor(msg: number) {
197
+ this.msg = msg;
198
+ }
199
+ }
200
+ // 错误写法,count未指定类型,编译报错
201
+ @ObjectLink count;
202
+ // 错误写法,Test未被@Observed装饰,编译报错
203
+ @ObjectLink test: Test;
204
+ ```
205
+
206
+ ```TypeScript
207
+ @Observed
208
+ class Info {
209
+ public count: number;
210
+
211
+ constructor(count: number) {
212
+ this.count = count;
213
+ }
214
+ }
215
+ // ...
216
+ // 正确写法
217
+ @ObjectLink count: Info;
218
+ ```
219
+
220
+ - @ObjectLink装饰的变量不能本地初始化,仅能通过构造参数从父组件传入初始值,否则编译时会报错。
221
+
222
+ ```ts
223
+ // 错误写法,编译报错
224
+ @ObjectLink count: CountInfo = new CountInfo(10);
225
+ ```
226
+
227
+ ```TypeScript
228
+ @Observed
229
+ class CountInfo {
230
+ public count: number;
231
+
232
+ constructor(count: number) {
233
+ this.count = count;
234
+ }
235
+ }
236
+ // ...
237
+ // 正确写法
238
+ @ObjectLink count: CountInfo;
239
+ ```
240
+
241
+ - @ObjectLink装饰的变量是只读的,不能被赋值,否则会有运行时报错提示Cannot set property when setter is undefined。如果需要对@ObjectLink装饰的变量进行整体替换,可以在父组件对其进行整体替换。
242
+
243
+ 【反例】
244
+
245
+ ```ts
246
+ @Observed
247
+ class Info {
248
+ count: number;
249
+
250
+ constructor(count: number) {
251
+ this.count = count;
252
+ }
253
+ }
254
+
255
+ @Component
256
+ struct Child {
257
+ @ObjectLink num: Info;
258
+
259
+ build() {
260
+ Column() {
261
+ Text(`num的值: ${this.num.count}`)
262
+ .onClick(() => {
263
+ // 错误写法,@ObjectLink装饰的变量不能被赋值,运行时报错
264
+ this.num = new Info(10);
265
+ })
266
+ }
267
+ }
268
+ }
269
+
270
+ @Entry
271
+ @Component
272
+ struct Parent {
273
+ @State num: Info = new Info(10);
274
+
275
+ build() {
276
+ Column() {
277
+ Text(`count的值: ${this.num.count}`)
278
+ Child({num: this.num})
279
+ }
280
+ }
281
+ }
282
+ ```
283
+
284
+ 【正例】
285
+
286
+ ```TypeScript
287
+ @Observed
288
+ class Info {
289
+ public count: number;
290
+
291
+ constructor(count: number) {
292
+ this.count = count;
293
+ }
294
+ }
295
+
296
+ @Component
297
+ struct Child {
298
+ @ObjectLink num: Info;
299
+
300
+ build() {
301
+ Column() {
302
+ Text(`num value: ${this.num.count}`)
303
+ .onClick(() => {
304
+ // 正确写法,可以更改@ObjectLink装饰变量的成员属性
305
+ this.num.count = 20;
306
+ })
307
+ }
308
+ }
309
+ }
310
+
311
+ @Entry
312
+ @Component
313
+ struct Parent {
314
+ @State num: Info = new Info(10);
315
+
316
+ build() {
317
+ Column() {
318
+ Text(`count value: ${this.num.count}`)
319
+ Button('click')
320
+ .onClick(() => {
321
+ // 可以在父组件做整体替换
322
+ this.num = new Info(30);
323
+ })
324
+ Child({ num: this.num })
325
+ }
326
+ }
327
+ }
328
+ ```
329
+ ## 使用场景 ### 对象类型
330
+ 该场景包含built-in类型(Array、Map、Set和Date)和普通class。从API version 19开始,@ObjectLink接收@State传递built-in类型和普通class对象,可以观察其API调用和第一层变化,无需额外添加@Observed装饰。因为@State等状态变量装饰器,会给对象(外层对象)添加一层“代理”包装,其功能等同于添加@Observed装饰。
331
+
332
+ ```TypeScript
333
+ class Book {
334
+ public name: string;
335
+
336
+ constructor(name: string) {
337
+ this.name = name;
338
+ }
339
+ }
340
+
341
+ @Component
342
+ struct BookCard {
343
+ @ObjectLink book: Book;
344
+
345
+ build() {
346
+ Column() {
347
+ Text(`BookCard: ${this.book.name}`) // 可以观察到name的变化
348
+ .width(320)
349
+ .margin(10)
350
+ .textAlign(TextAlign.Center)
351
+
352
+ Button('change book.name')
353
+ .width(320)
354
+ .margin(10)
355
+ .onClick(() => {
356
+ this.book.name = 'C++';
357
+ })
358
+ }
359
+ }
360
+ }
361
+
362
+ @Entry
363
+ @Component
364
+ struct Index {
365
+ @State book: Book = new Book('JS');
366
+
367
+ build() {
368
+ Column() {
369
+ BookCard({ book: this.book })
370
+ }
371
+ }
372
+ }
373
+ ```
374
+ ### 嵌套对象
375
+ ```TypeScript
376
+ @Observed
377
+ class Book {
378
+ public name: string;
379
+
380
+ constructor(name: string) {
381
+ this.name = name;
382
+ }
383
+ }
384
+
385
+ @Observed
386
+ class Bag {
387
+ public book: Book;
388
+
389
+ constructor(book: Book) {
390
+ this.book = book;
391
+ }
392
+ }
393
+
394
+ @Component
395
+ struct BookCard {
396
+ @ObjectLink book: Book;
397
+
398
+ build() {
399
+ Column() {
400
+ Text(`BookCard: ${this.book.name}`) // 可以观察到name的变化
401
+ .width(320)
402
+ .margin(10)
403
+ .textAlign(TextAlign.Center)
404
+
405
+ Button('change book.name')
406
+ .width(320)
407
+ .margin(10)
408
+ .onClick(() => {
409
+ this.book.name = 'C++';
410
+ })
411
+ }
412
+ }
413
+ }
414
+
415
+ @Entry
416
+ @Component
417
+ struct Index {
418
+ @State bag: Bag = new Bag(new Book('JS'));
419
+
420
+ build() {
421
+ Column() {
422
+ Text(`Index: ${this.bag.book.name}`) // 无法观察到name的变化
423
+ .width(320)
424
+ .margin(10)
425
+ .textAlign(TextAlign.Center)
426
+
427
+ Button('change bag.book.name')
428
+ .width(320)
429
+ .margin(10)
430
+ .onClick(() => {
431
+ this.bag.book.name = 'TS';
432
+ })
433
+
434
+ BookCard({ book: this.bag.book })
435
+ }
436
+ }
437
+ }
438
+ ```
439
+
440
+ ![]((https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/a6/v3/Mfrc6s9BTL6iZSuF4Y5TZw/zh-cn_image_0000002535788144.gif)
441
+
442
+ 上述示例中:
443
+
444
+ - 点击change bag.book.name,Index组件中的Text组件不刷新,因为该变化属于第二层的变化,@State无法观察到第二层的变化。然而,Book被@Observed装饰,Book的属性name可以被@ObjectLink观察到,所以BookCard组件中Text组件可以刷新。
445
+ - 点击change book.name,Bookcard组件中的Text组件刷新,因为该变化在BooKCard中属于第一层的变化,亦可被@ObjectLink观察到。 ### 对象数组
446
+ 对象数组是一种常用的数据结构。以下示例展示了对象数组的用法。
447
+
448
+ > **说明:**
449
+ >
450
+ > NextID是用来在[ForEach循环渲染](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-rendering-control-foreach)过程中,为每个数组元素生成一个唯一且持久的键值,标识对应的组件。
451
+ >
452
+
453
+ ```TypeScript
454
+ import { hilog } from '@kit.PerformanceAnalysisKit';
455
+
456
+ const DOMAIN = 0x0001;
457
+ const TAG = 'ArkTSObservedAndObjectlink';
458
+ let nextID: number = 1;
459
+
460
+ @Observed
461
+ class Info {
462
+ public id: number;
463
+ public info: number;
464
+
465
+ constructor(info: number) {
466
+ this.id = nextID++;
467
+ this.info = info;
468
+ }
469
+ }
470
+
471
+ @Component
472
+ struct Child {
473
+ // 子组件Child的@ObjectLink的类型是Info
474
+ @ObjectLink info: Info;
475
+ label: string = 'ViewChild';
476
+
477
+ build() {
478
+ Row() {
479
+ Button(`ViewChild [${this.label}] this.info.info = ${this.info ? this.info.info : 'undefined'}`)
480
+ .width(320)
481
+ .margin(10)
482
+ .onClick(() => {
483
+ this.info.info += 1;
484
+ })
485
+ }
486
+ }
487
+ }
488
+
489
+ @Entry
490
+ @Component
491
+ struct Parent {
492
+ // Parent中有@State装饰的Info[]
493
+ @State arrA: Info[] = [new Info(0), new Info(0)];
494
+
495
+ build() {
496
+ Column() {
497
+ ForEach(this.arrA,
498
+ (item: Info) => {
499
+ Child({ label: `#${item.id}`, info: item })
500
+ },
501
+ (item: Info): string => item.id.toString()
502
+ )
503
+ // 使用@State装饰的数组的数组项初始化@ObjectLink,其中数组项是被@Observed装饰的Info的实例
504
+ Child({ label: 'ViewChild this.arrA[first]', info: this.arrA[0] })
505
+ Child({ label: 'ViewChild this.arrA[last]', info: this.arrA[this.arrA.length-1] })
506
+
507
+ Button('ViewParent: reset array')
508
+ .width(320)
509
+ .margin(10)
510
+ .onClick(() => {
511
+ this.arrA = [new Info(0), new Info(0)];
512
+ })
513
+ Button('ViewParent: push')
514
+ .width(320)
515
+ .margin(10)
516
+ .onClick(() => {
517
+ this.arrA.push(new Info(0));
518
+ })
519
+ Button('ViewParent: shift')
520
+ .width(320)
521
+ .margin(10)
522
+ .onClick(() => {
523
+ if (this.arrA.length > 0) {
524
+ this.arrA.shift();
525
+ } else {
526
+ hilog.info(DOMAIN, TAG, 'length <= 0');
527
+ }
528
+ })
529
+ Button('ViewParent: item property in middle')
530
+ .width(320)
531
+ .margin(10)
532
+ .onClick(() => {
533
+ this.arrA[Math.floor(this.arrA.length / 2)].info = 10;
534
+ })
535
+ Button('ViewParent: item property in middle')
536
+ .width(320)
537
+ .margin(10)
538
+ .onClick(() => {
539
+ this.arrA[Math.floor(this.arrA.length / 2)] = new Info(11);
540
+ })
541
+ }
542
+ }
543
+ }
544
+ ```
545
+
546
+ ![]((https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/95/v3/bdYiAtkIRUuxs2IGGOUUPw/zh-cn_image_0000002535948090.gif)
547
+
548
+ - this.arrA[Math.floor(this.arrA.length/2)] = new Info(..) :该状态变量的改变触发2次更新:
549
+
550
+ - ForEach:数组项的赋值导致ForEach的[itemGenerator](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-rendering-control-foreach)被修改,因此数组项被识别为有更改,ForEach的item builder将执行,创建新的Child组件实例。
551
+ - Child({ label: 'ViewChild this.arrA[last]', info: this.arrA[this.arrA.length-1] }):上述更改改变了数组中第二个元素,所以绑定this.arrA[1]的Child将被更新。
552
+ - this.arrA.push(new Info(0)) : 将触发2次不同效果的更新:
553
+
554
+ - ForEach:新添加的Info对象对于ForEach是未知的[itemGenerator](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-rendering-control-foreach),ForEach的item builder将执行,创建新的Child组件实例。
555
+ - Child({ label: 'ViewChild this.arrA[last]', info: this.arrA[this.arrA.length-1] }):数组的最后一项有更改,因此引起第二个Child的实例的更改。对于Child({ label: 'ViewChild this.arrA[first]', info: this.arrA[0] }),数组的更改并没有触发一个数组项更改的改变,所以第一个Child不会刷新。
556
+ - this.arrA[Math.floor(this.arrA.length/2)].info:@State无法观察到第二层的变化,但是Info被@Observed装饰,Info的属性的变化将被@ObjectLink观察到。
557
+ ### 二维数组
558
+ 使用@Observed观察二维数组的变化。可以声明一个被@Observed装饰的继承Array的子类。
559
+
560
+ ```TypeScript
561
+ @Observed
562
+ class ObservedArray<T> extends Array<T> {
563
+ }
564
+ ```
565
+
566
+ 声明一个继承自Array的类ObservedArray<T>,并使用new操作符创建ObservedArray<string>的实例,该实例可以观察到属性变化。
567
+
568
+ 在下面的示例中,展示了如何利用@Observed观察二维数组的变化。
569
+
570
+ ```TypeScript
571
+ @Observed
572
+ class ObservedArray<T> extends Array<T> {
573
+ }
574
+
575
+ @Component
576
+ struct Item {
577
+ @ObjectLink itemArr: ObservedArray<string>;
578
+
579
+ build() {
580
+ Row() {
581
+ ForEach(this.itemArr, (item: string, index: number) => {
582
+ Text(`${index}: ${item}`)
583
+ .width(100)
584
+ .height(100)
585
+ }, (item: string) => item)
586
+ }
587
+ }
588
+ }
589
+
590
+ @Entry
591
+ @Component
592
+ struct IndexPage {
593
+ @State arr: Array<ObservedArray<string>> = [
594
+ new ObservedArray<string>('apple'),
595
+ new ObservedArray<string>('banana'),
596
+ new ObservedArray<string>('orange')
597
+ ];
598
+
599
+ build() {
600
+ Column() {
601
+ ForEach(this.arr, (itemArr: ObservedArray<string>) => {
602
+ Item({ itemArr: itemArr })
603
+ })
604
+
605
+ Divider()
606
+
607
+ Button('push two-dimensional array item')
608
+ .margin(10)
609
+ .onClick(() => {
610
+ this.arr[0].push('strawberry');
611
+ })
612
+
613
+ Button('push array item')
614
+ .margin(10)
615
+ .onClick(() => {
616
+ this.arr.push(new ObservedArray<string>('pear'));
617
+ })
618
+
619
+ Button('change two-dimensional array first item')
620
+ .margin(10)
621
+ .onClick(() => {
622
+ this.arr[0][0] = 'APPLE';
623
+ })
624
+
625
+ Button('change array first item')
626
+ .margin(10)
627
+ .onClick(() => {
628
+ this.arr[0] = new ObservedArray<string>('watermelon');
629
+ })
630
+ }
631
+ }
632
+ }
633
+ ```
634
+
635
+ API version 19及以后,@ObjectLink也可以被[makeV1Observed](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-statemanagement#makev1observed19)的返回值初始化。所以开发者如果不想额外声明继承Array的类,也可以使用makeV1Observed来达到同样的效果。
636
+
637
+ 完整例子如下。
638
+
639
+ ```TypeScript
640
+ import { UIUtils } from '@kit.ArkUI';
641
+
642
+ @Component
643
+ struct Item {
644
+ @ObjectLink itemArr: Array<string>;
645
+
646
+ build() {
647
+ Row() {
648
+ ForEach(this.itemArr, (item: string, index: number) => {
649
+ Text(`${index}: ${item}`)
650
+ .width(100)
651
+ .height(100)
652
+ }, (item: string) => item)
653
+ }
654
+ }
655
+ }
656
+
657
+ @Entry
658
+ @Component
659
+ struct IndexPage {
660
+ @State arr: Array<Array<string>> =
661
+ [UIUtils.makeV1Observed(['apple']), UIUtils.makeV1Observed(['banana']), UIUtils.makeV1Observed(['orange'])];
662
+
663
+ build() {
664
+ Column() {
665
+ ForEach(this.arr, (itemArr: Array<string>) => {
666
+ Item({ itemArr: itemArr })
667
+ })
668
+
669
+ Divider()
670
+
671
+ Button('push two-dimensional array item')
672
+ .margin(10)
673
+ .onClick(() => {
674
+ this.arr[0].push('strawberry');
675
+ })
676
+
677
+ Button('push array item')
678
+ .margin(10)
679
+ .onClick(() => {
680
+ this.arr.push(UIUtils.makeV1Observed(['pear']));
681
+ })
682
+
683
+ Button('change two-dimensional array first item')
684
+ .margin(10)
685
+ .onClick(() => {
686
+ this.arr[0][0] = 'APPLE';
687
+ })
688
+
689
+ Button('change array first item')
690
+ .margin(10)
691
+ .onClick(() => {
692
+ this.arr[0] = UIUtils.makeV1Observed(['watermelon']);
693
+ })
694
+ }
695
+ }
696
+ }
697
+ ```
698
+
699
+ ![]((https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/2e/v3/nIoLJSPYTgy8mbrAtl--Aw/zh-cn_image_0000002566867923.gif)
700
+ ### 继承Map类
701
+ > **说明:**
702
+ >
703
+ > 从API version 11开始,@ObjectLink支持@Observed装饰Map类型和继承Map类的类型。
704
+ >
705
+
706
+ 在下面的示例中,myMap类型为MyMap<number, string>,点击Button改变myMap的属性,视图会随之刷新。
707
+
708
+ ```TypeScript
709
+ @Observed
710
+ class Info {
711
+ public info: MyMap<number, string>;
712
+
713
+ constructor(info: MyMap<number, string>) {
714
+ this.info = info;
715
+ }
716
+ }
717
+
718
+ @Observed
719
+ export class MyMap<K, V> extends Map<K, V> {
720
+ public name: string;
721
+
722
+ constructor(name?: string, args?: [K, V][]) {
723
+ super(args);
724
+ this.name = name ? name : 'My Map';
725
+ }
726
+
727
+ getName() {
728
+ return this.name;
729
+ }
730
+ }
731
+
732
+ @Entry
733
+ @Component
734
+ struct MapSampleNested {
735
+ @State message: Info = new Info(new MyMap('myMap', [[0, 'a'], [1, 'b'], [3, 'c']]));
736
+
737
+ build() {
738
+ Row() {
739
+ Column() {
740
+ MapSampleNestedChild({ myMap: this.message.info })
741
+ }
742
+ .width('100%')
743
+ }
744
+ .height('100%')
745
+ }
746
+ }
747
+
748
+ @Component
749
+ struct MapSampleNestedChild {
750
+ @ObjectLink myMap: MyMap<number, string>;
751
+
752
+ build() {
753
+ Row() {
754
+ Column() {
755
+ ForEach(Array.from(this.myMap.entries()), (item: [number, string]) => {
756
+ Text(`${item[0]}`).fontSize(30)
757
+ Text(`${item[1]}`).fontSize(30)
758
+ Divider().strokeWidth(5)
759
+ })
760
+
761
+ Button('set new one')
762
+ .width(200)
763
+ .margin(10)
764
+ .onClick(() => {
765
+ this.myMap.set(4, 'd');
766
+ })
767
+ Button('clear')
768
+ .width(200)
769
+ .margin(10)
770
+ .onClick(() => {
771
+ this.myMap.clear();
772
+ })
773
+ Button('replace the first one')
774
+ .width(200)
775
+ .margin(10)
776
+ .onClick(() => {
777
+ this.myMap.set(0, 'aa');
778
+ })
779
+ Button('delete the first one')
780
+ .width(200)
781
+ .margin(10)
782
+ .onClick(() => {
783
+ this.myMap.delete(0);
784
+ })
785
+ }
786
+ .width('100%')
787
+ }
788
+ .height('100%')
789
+ }
790
+ }
791
+ ```
792
+
793
+ ![]((https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/c2/v3/hQT5d4MLR4qssP_m-JYnow/zh-cn_image_0000002566707941.gif)
794
+ ### 继承Set类
795
+ > **说明:**
796
+ >
797
+ > 从API version 11开始,@ObjectLink支持@Observed装饰Set类型和继承Set类的类型。
798
+ >
799
+
800
+ 在下面的示例中,mySet类型为MySet<number>,点击Button改变mySet的属性,视图会随之刷新。
801
+
802
+ ```TypeScript
803
+ @Observed
804
+ class Info {
805
+ public info: MySet<number>;
806
+
807
+ constructor(info: MySet<number>) {
808
+ this.info = info;
809
+ }
810
+ }
811
+
812
+ @Observed
813
+ export class MySet<T> extends Set<T> {
814
+ public name: string;
815
+
816
+ constructor(name?: string, args?: T[]) {
817
+ super(args);
818
+ this.name = name ? name : 'My Set';
819
+ }
820
+
821
+ getName() {
822
+ return this.name;
823
+ }
824
+ }
825
+
826
+ @Entry
827
+ @Component
828
+ struct SetSampleNested {
829
+ @State message: Info = new Info(new MySet('Set', [0, 1, 2, 3, 4]));
830
+
831
+ build() {
832
+ Row() {
833
+ Column() {
834
+ SetSampleNestedChild({ mySet: this.message.info })
835
+ }
836
+ .width('100%')
837
+ }
838
+ .height('100%')
839
+ }
840
+ }
841
+
842
+ @Component
843
+ struct SetSampleNestedChild {
844
+ @ObjectLink mySet: MySet<number>;
845
+
846
+ build() {
847
+ Row() {
848
+ Column() {
849
+ ForEach(Array.from(this.mySet.entries()), (item: [number, number]) => {
850
+ Text(`${item}`).fontSize(30)
851
+ Divider()
852
+ })
853
+ Button('set new one')
854
+ .width(200)
855
+ .margin(10)
856
+ .onClick(() => {
857
+ this.mySet.add(5);
858
+ })
859
+ Button('clear')
860
+ .width(200)
861
+ .margin(10)
862
+ .onClick(() => {
863
+ this.mySet.clear();
864
+ })
865
+ Button('delete the first one')
866
+ .width(200)
867
+ .margin(10)
868
+ .onClick(() => {
869
+ this.mySet.delete(0);
870
+ })
871
+ }
872
+ .width('100%')
873
+ }
874
+ .height('100%')
875
+ }
876
+ }
877
+ ```
878
+
879
+ ![]((https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/d4/v3/ZpEtVAVEThqbWRPs_EWgKg/zh-cn_image_0000002535788146.gif)
880
+ ### ObjectLink支持联合类型
881
+ @ObjectLink支持@Observed装饰类和undefined或null组成的联合类型,在下面的示例中,count类型为Source | Data | undefined,点击父组件Parent中的Button改变count的属性或者类型,Child组件中对应的Text组件刷新。
882
+
883
+ ```TypeScript
884
+ import { hilog } from '@kit.PerformanceAnalysisKit';
885
+
886
+ const DOMAIN = 0x0001;
887
+ const TAG = 'ArkTSObservedAndObjectlink';
888
+
889
+ @Observed
890
+ class Source {
891
+ public source: number;
892
+
893
+ constructor(source: number) {
894
+ this.source = source;
895
+ }
896
+ }
897
+
898
+ @Observed
899
+ class Data {
900
+ public data: number;
901
+
902
+ constructor(data: number) {
903
+ this.data = data;
904
+ }
905
+ }
906
+
907
+ @Entry
908
+ @Component
909
+ struct Parent {
910
+ @State count: Source | Data | undefined = new Source(10);
911
+
912
+ build() {
913
+ Column() {
914
+ Child({ count: this.count })
915
+
916
+ Button('change count property')
917
+ .margin(10)
918
+ .onClick(() => {
919
+ // 判断count的类型,做属性的更新
920
+ if (this.count instanceof Source) {
921
+ this.count.source += 1;
922
+ } else if (this.count instanceof Data) {
923
+ this.count.data += 1;
924
+ } else {
925
+ hilog.info(DOMAIN, TAG, `count is undefined, cannot change property`);
926
+ }
927
+ })
928
+
929
+ Button('change count to Source')
930
+ .margin(10)
931
+ .onClick(() => {
932
+ // 赋值为Source的实例
933
+ this.count = new Source(100);
934
+ })
935
+
936
+ Button('change count to Data')
937
+ .margin(10)
938
+ .onClick(() => {
939
+ // 赋值为Data的实例
940
+ this.count = new Data(100);
941
+ })
942
+
943
+ Button('change count to undefined')
944
+ .margin(10)
945
+ .onClick(() => {
946
+ // 赋值为undefined
947
+ this.count = undefined;
948
+ })
949
+ }.width('100%')
950
+ }
951
+ }
952
+
953
+ @Component
954
+ struct Child {
955
+ @ObjectLink count: Source | Data | undefined;
956
+
957
+ build() {
958
+ Column() {
959
+ Text(`count is instanceof ${this.count instanceof Source ? 'Source' :
960
+ this.count instanceof Data ? 'Data' : 'undefined'}`)
961
+ .fontSize(30)
962
+ .margin(10)
963
+
964
+ Text(`count's property is ${this.count instanceof Source ? this.count.source : this.count?.data}`).fontSize(15)
965
+
966
+ }.width('100%')
967
+ }
968
+ }
969
+ ```
970
+
971
+ ![]((https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/51/v3/AoxB_xL9QoaxbTqPe8e2jw/zh-cn_image_0000002535948092.gif)
972
+ ## 常见问题 ### 基础嵌套对象属性更改失效
973
+ 在应用开发中,有很多嵌套对象场景,例如,开发者更新了某个属性,但UI没有进行对应的更新。
974
+
975
+ 每个装饰器都有观察能力,但并非所有的改变都可以被观察到,只有可以被观察到的变化才会触发UI更新。@Observed装饰器可以观察到嵌套对象的属性变化,其他装饰器仅能观察到第一层的变化。
976
+
977
+ 【反例】
978
+
979
+ 下面的例子中,一些UI组件并不会更新。
980
+
981
+ ```ts
982
+ class Parent {
983
+ parentId: number;
984
+
985
+ constructor(parentId: number) {
986
+ this.parentId = parentId;
987
+ }
988
+
989
+ getParentId(): number {
990
+ return this.parentId;
991
+ }
992
+
993
+ setParentId(parentId: number): void {
994
+ this.parentId = parentId;
995
+ }
996
+ }
997
+
998
+ class Child {
999
+ childId: number;
1000
+
1001
+ constructor(childId: number) {
1002
+ this.childId = childId;
1003
+ }
1004
+
1005
+ getChildId(): number {
1006
+ return this.childId;
1007
+ }
1008
+
1009
+ setChildId(childId: number): void {
1010
+ this.childId = childId;
1011
+ }
1012
+ }
1013
+
1014
+ class Cousin extends Parent {
1015
+ cousinId: number = 47;
1016
+ child: Child;
1017
+
1018
+ constructor(parentId: number, cousinId: number, childId: number) {
1019
+ super(parentId);
1020
+ this.cousinId = cousinId;
1021
+ this.child = new Child(childId);
1022
+ }
1023
+
1024
+ getCousinId(): number {
1025
+ return this.cousinId;
1026
+ }
1027
+
1028
+ setCousinId(cousinId: number): void {
1029
+ this.cousinId = cousinId;
1030
+ }
1031
+
1032
+ getChild(): number {
1033
+ return this.child.getChildId();
1034
+ }
1035
+
1036
+ setChild(childId: number): void {
1037
+ this.child.setChildId(childId);
1038
+ }
1039
+ }
1040
+
1041
+ @Entry
1042
+ @Component
1043
+ struct MyView {
1044
+ @State cousin: Cousin = new Cousin(10, 20, 30);
1045
+
1046
+ build() {
1047
+ Column({ space: 10 }) {
1048
+ Text(`parentId: ${this.cousin.parentId}`)
1049
+ Button('Change Parent.parent')
1050
+ .onClick(() => {
1051
+ this.cousin.parentId += 1;
1052
+ })
1053
+
1054
+ Text(`cousinId: ${this.cousin.cousinId}`)
1055
+ Button('Change Cousin.cousinId')
1056
+ .onClick(() => {
1057
+ this.cousin.cousinId += 1;
1058
+ })
1059
+
1060
+ Text(`childId: ${this.cousin.child.childId}`)
1061
+ Button('Change Cousin.Child.childId')
1062
+ .onClick(() => {
1063
+ // 点击时上面的Text组件不会刷新
1064
+ this.cousin.child.childId += 1;
1065
+ })
1066
+ }
1067
+ }
1068
+ }
1069
+ ```
1070
+
1071
+ - 最后一个Text组件Text('child: ${this.cousin.child.childId}'),当点击该组件时UI不会刷新。 因为,@State cousin : Cousin 只能观察到this.cousin属性的变化,比如this.cousin.parentId, this.cousin.cousinId 和this.cousin.child的变化,但是无法观察嵌套在属性中的属性,即this.cousin.child.childId(属性childId是内嵌在cousin中的对象Child的属性)。
1072
+
1073
+ - 为了观察到嵌套于内部的Child的属性,需要做如下改变:
1074
+
1075
+ - 构造一个子组件,用于单独渲染Child的实例。 该子组件可以使用@ObjectLink child : Child或@Prop child : Child。通常会使用@ObjectLink,除非子组件需要对其Child对象进行本地修改。
1076
+ - 嵌套的Child必须用@Observed装饰。当在Cousin中创建Child对象时(本示例中的Cousin(10, 20, 30)),它将被包装在ES6代理中,当Child属性更改时(this.cousin.child.childId += 1),该代码将修改通知到@ObjectLink变量。
1077
+ 【正例】
1078
+
1079
+ 以下示例使用@Observed/@ObjectLink来观察嵌套对象的属性更改。
1080
+
1081
+ ```TypeScript
1082
+ class Parent {
1083
+ public parentId: number;
1084
+
1085
+ constructor(parentId: number) {
1086
+ this.parentId = parentId;
1087
+ }
1088
+
1089
+ getParentId(): number {
1090
+ return this.parentId;
1091
+ }
1092
+
1093
+ setParentId(parentId: number): void {
1094
+ this.parentId = parentId;
1095
+ }
1096
+ }
1097
+
1098
+ @Observed
1099
+ class Child {
1100
+ public childId: number;
1101
+
1102
+ constructor(childId: number) {
1103
+ this.childId = childId;
1104
+ }
1105
+
1106
+ getChildId(): number {
1107
+ return this.childId;
1108
+ }
1109
+
1110
+ setChildId(childId: number): void {
1111
+ this.childId = childId;
1112
+ }
1113
+ }
1114
+
1115
+ class Cousin extends Parent {
1116
+ public cousinId: number = 47;
1117
+ public child: Child;
1118
+
1119
+ constructor(parentId: number, cousinId: number, childId: number) {
1120
+ super(parentId);
1121
+ this.cousinId = cousinId;
1122
+ this.child = new Child(childId);
1123
+ }
1124
+
1125
+ getCousinId(): number {
1126
+ return this.cousinId;
1127
+ }
1128
+
1129
+ setCousinId(cousinId: number): void {
1130
+ this.cousinId = cousinId;
1131
+ }
1132
+
1133
+ getChild(): number {
1134
+ return this.child.getChildId();
1135
+ }
1136
+
1137
+ setChild(childId: number): void {
1138
+ this.child.setChildId(childId);
1139
+ }
1140
+ }
1141
+
1142
+ @Component
1143
+ struct ViewChild {
1144
+ @ObjectLink child: Child;
1145
+
1146
+ build() {
1147
+ Column({ space: 10 }) {
1148
+ Text(`childId: ${this.child.getChildId()}`)
1149
+ Button('Change childId')
1150
+ .onClick(() => {
1151
+ this.child.setChildId(this.child.getChildId() + 1);
1152
+ })
1153
+ }
1154
+ }
1155
+ }
1156
+
1157
+ @Entry
1158
+ @Component
1159
+ struct MyView {
1160
+ @State cousin: Cousin = new Cousin(10, 20, 30);
1161
+
1162
+ build() {
1163
+ Column({ space: 10 }) {
1164
+ Text(`parentId: ${this.cousin.parentId}`)
1165
+ Button('Change Parent.parentId')
1166
+ .onClick(() => {
1167
+ this.cousin.parentId += 1;
1168
+ })
1169
+
1170
+ Text(`cousinId: ${this.cousin.cousinId}`)
1171
+ Button('Change Cousin.cousinId')
1172
+ .onClick(() => {
1173
+ this.cousin.cousinId += 1;
1174
+ })
1175
+
1176
+ ViewChild({ child: this.cousin.child }) // Text(`childId: ${this.cousin.child.childId}`)的替代写法
1177
+ Button('Change Cousin.Child.childId')
1178
+ .onClick(() => {
1179
+ this.cousin.child.childId += 1;
1180
+ })
1181
+ }
1182
+ }
1183
+ }
1184
+ ```
1185
+ ### 复杂嵌套对象属性更改失效
1186
+ 【反例】
1187
+
1188
+ 以下示例创建了一个带有@ObjectLink装饰变量的子组件,用于渲染一个含有嵌套属性的ParentCounter,用@Observed装饰嵌套在ParentCounter中的SubCounter。
1189
+
1190
+ ```ts
1191
+ let nextId = 1;
1192
+ @Observed
1193
+ class SubCounter {
1194
+ counter: number;
1195
+ constructor(c: number) {
1196
+ this.counter = c;
1197
+ }
1198
+ }
1199
+ @Observed
1200
+ class ParentCounter {
1201
+ id: number;
1202
+ counter: number;
1203
+ subCounter: SubCounter;
1204
+ incrCounter() {
1205
+ this.counter++;
1206
+ }
1207
+ incrSubCounter(c: number) {
1208
+ this.subCounter.counter += c;
1209
+ }
1210
+ setSubCounter(c: number): void {
1211
+ this.subCounter.counter = c;
1212
+ }
1213
+ constructor(c: number) {
1214
+ this.id = nextId++;
1215
+ this.counter = c;
1216
+ this.subCounter = new SubCounter(c);
1217
+ }
1218
+ }
1219
+ @Component
1220
+ struct CounterComp {
1221
+ @ObjectLink value: ParentCounter;
1222
+ build() {
1223
+ Column({ space: 10 }) {
1224
+ Text(`${this.value.counter}`)
1225
+ .fontSize(25)
1226
+ .onClick(() => {
1227
+ this.value.incrCounter();
1228
+ })
1229
+ Text(`${this.value.subCounter.counter}`)
1230
+ .onClick(() => {
1231
+ this.value.incrSubCounter(1);
1232
+ })
1233
+ Divider().height(2)
1234
+ }
1235
+ }
1236
+ }
1237
+ @Entry
1238
+ @Component
1239
+ struct ParentComp {
1240
+ @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
1241
+ build() {
1242
+ Row() {
1243
+ Column() {
1244
+ CounterComp({ value: this.counter[0] })
1245
+ CounterComp({ value: this.counter[1] })
1246
+ CounterComp({ value: this.counter[2] })
1247
+ Divider().height(5)
1248
+ ForEach(this.counter,
1249
+ (item: ParentCounter) => {
1250
+ CounterComp({ value: item })
1251
+ },
1252
+ (item: ParentCounter) => item.id.toString()
1253
+ )
1254
+ Divider().height(5)
1255
+ // 第一个点击事件
1256
+ Text('Parent: incr counter[0].counter')
1257
+ .fontSize(20).height(50)
1258
+ .onClick(() => {
1259
+ this.counter[0].incrCounter();
1260
+ // 每次触发时自增10
1261
+ this.counter[0].incrSubCounter(10);
1262
+ })
1263
+ // 第二个点击事件
1264
+ Text('Parent: set.counter to 10')
1265
+ .fontSize(20).height(50)
1266
+ .onClick(() => {
1267
+ // 无法将value设置为10,UI不会刷新
1268
+ this.counter[0].setSubCounter(10);
1269
+ })
1270
+ Text('Parent: reset entire counter')
1271
+ .fontSize(20).height(50)
1272
+ .onClick(() => {
1273
+ this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
1274
+ })
1275
+ }
1276
+ }
1277
+ }
1278
+ }
1279
+ ```
1280
+
1281
+ 对于Text('Parent: incr counter[0].counter')的onClick事件,this.counter[0].incrSubCounter(10)调用incrSubCounter方法使SubCounter的counter值增加10,UI同步刷新。
1282
+
1283
+ 然而,在Text('Parent: set.counter to 10')的onClick中调用this.counter[0].setSubCounter(10)时,SubCounter的counter值无法重置为10。
1284
+
1285
+ incrSubCounter和setSubCounter都是同一个SubCounter的函数。在第一个点击处理时调用incrSubCounter可以正确更新UI,而第二个点击处理调用setSubCounter时却没有更新UI。实际上incrSubCounter和setSubCounter两个函数都不能触发Text('${this.value.subCounter.counter}')的更新,因为@ObjectLink value : ParentCounter仅能观察其代理ParentCounter的属性,对于this.value.subCounter.counter是SubCounter的属性,无法观察到嵌套类的属性。
1286
+
1287
+ 另外,第一个click事件调用this.counter[0].incrCounter()将CounterComp自定义组件中的@ObjectLink value: ParentCounter标记为已更改,会触发Text('${this.value.subCounter.counter}')的更新。如果在第一个点击事件中删除this.counter[0].incrCounter(),则无法更新UI。
1288
+
1289
+ 【正例】
1290
+
1291
+ 对于上述问题,为了直接观察SubCounter中的属性,以便this.counter[0].setSubCounter(10)操作有效,可以利用下面的方法:
1292
+
1293
+ ```TypeScript
1294
+ let nextId = 1;
1295
+
1296
+ @Observed
1297
+ class SubCounter {
1298
+ public counter: number;
1299
+
1300
+ constructor(c: number) {
1301
+ this.counter = c;
1302
+ }
1303
+ }
1304
+
1305
+ @Observed
1306
+ class ParentCounter {
1307
+ public id: number;
1308
+ public counter: number;
1309
+ public subCounter: SubCounter;
1310
+
1311
+ incrCounter() {
1312
+ this.counter++;
1313
+ }
1314
+
1315
+ incrSubCounter(c: number) {
1316
+ this.subCounter.counter += c;
1317
+ }
1318
+
1319
+ setSubCounter(c: number): void {
1320
+ this.subCounter.counter = c;
1321
+ }
1322
+
1323
+ constructor(c: number) {
1324
+ this.id = nextId++;
1325
+ this.counter = c;
1326
+ this.subCounter = new SubCounter(c);
1327
+ }
1328
+ }
1329
+
1330
+
1331
+ @Entry
1332
+ @Component
1333
+ struct ParentComp {
1334
+ @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
1335
+ build() {
1336
+ Row() {
1337
+ CounterComp({ value: this.counter[0] }) // ParentComp组件传递 ParentCounter 给 CounterComp 组件
1338
+ }
1339
+ }
1340
+ }
1341
+
1342
+ @Component
1343
+ struct CounterComp {
1344
+ @ObjectLink value: ParentCounter; // @ObjectLink 接收 ParentCounter
1345
+ build() {
1346
+ // CounterChild 是 CounterComp 的子组件,CounterComp 传递 this.value.subCounter 给 CounterChild 组件
1347
+ CounterChild({ subValue: this.value.subCounter })
1348
+ }
1349
+ }
1350
+
1351
+ @Component
1352
+ struct CounterChild {
1353
+ @ObjectLink subValue: SubCounter; // @ObjectLink 接收 SubCounter
1354
+ build() {
1355
+ Text(`${this.subValue.counter}`)
1356
+ .onClick(() => {
1357
+ this.subValue.counter += 1;
1358
+ })
1359
+ }
1360
+ }
1361
+ ```
1362
+
1363
+ 该方法使得@ObjectLink分别代理了ParentCounter和SubCounter的属性,这样对于这两个类的属性的变化都可以观察到,即都会对UI视图进行刷新。即使删除了上面所说的this.counter[0].incrCounter(),UI也会进行正确的刷新。
1364
+
1365
+ 该方法可用于实现“两个层级”的观察,即外部对象和内部嵌套对象的观察。但是该方法只能用于@ObjectLink装饰器,无法作用于@Prop(@Prop通过深拷贝传入对象)。详情参考[@Prop与@ObjectLink的差异](#prop与objectlink的差异)。
1366
+
1367
+ ```TypeScript
1368
+ let nextId = 1;
1369
+
1370
+ @Observed
1371
+ class SubCounter {
1372
+ public counter: number;
1373
+
1374
+ constructor(c: number) {
1375
+ this.counter = c;
1376
+ }
1377
+ }
1378
+
1379
+ @Observed
1380
+ class ParentCounter {
1381
+ public id: number;
1382
+ public counter: number;
1383
+ public subCounter: SubCounter;
1384
+
1385
+ incrCounter() {
1386
+ this.counter++;
1387
+ }
1388
+
1389
+ incrSubCounter(c: number) {
1390
+ this.subCounter.counter += c;
1391
+ }
1392
+
1393
+ setSubCounter(c: number): void {
1394
+ this.subCounter.counter = c;
1395
+ }
1396
+
1397
+ constructor(c: number) {
1398
+ this.id = nextId++;
1399
+ this.counter = c;
1400
+ this.subCounter = new SubCounter(c);
1401
+ }
1402
+ }
1403
+
1404
+ @Component
1405
+ struct CounterComp {
1406
+ @ObjectLink value: ParentCounter;
1407
+
1408
+ build() {
1409
+ Column({ space: 10 }) {
1410
+ Text(`${this.value.counter}`)
1411
+ .fontSize(25)
1412
+ .onClick(() => {
1413
+ this.value.incrCounter();
1414
+ })
1415
+ CounterChild({ subValue: this.value.subCounter })
1416
+ Divider().height(2)
1417
+ }
1418
+ }
1419
+ }
1420
+
1421
+ @Component
1422
+ struct CounterChild {
1423
+ @ObjectLink subValue: SubCounter;
1424
+
1425
+ build() {
1426
+ Text(`${this.subValue.counter}`)
1427
+ .onClick(() => {
1428
+ this.subValue.counter += 1;
1429
+ })
1430
+ }
1431
+ }
1432
+
1433
+ @Entry
1434
+ @Component
1435
+ struct ParentComp {
1436
+ @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
1437
+
1438
+ build() {
1439
+ Row() {
1440
+ Column() {
1441
+ CounterComp({ value: this.counter[0] })
1442
+ CounterComp({ value: this.counter[1] })
1443
+ CounterComp({ value: this.counter[2] })
1444
+ Divider().height(5)
1445
+ ForEach(this.counter,
1446
+ (item: ParentCounter) => {
1447
+ CounterComp({ value: item })
1448
+ },
1449
+ (item: ParentCounter) => item.id.toString()
1450
+ )
1451
+ Divider().height(5)
1452
+ Text('Parent: reset entire counter')
1453
+ .fontSize(20).height(50)
1454
+ .onClick(() => {
1455
+ this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
1456
+ })
1457
+ Text('Parent: incr counter[0].counter')
1458
+ .fontSize(20).height(50)
1459
+ .onClick(() => {
1460
+ this.counter[0].incrCounter();
1461
+ this.counter[0].incrSubCounter(10);
1462
+ })
1463
+ Text('Parent: set.counter to 10')
1464
+ .fontSize(20).height(50)
1465
+ .onClick(() => {
1466
+ this.counter[0].setSubCounter(10);
1467
+ })
1468
+ }
1469
+ }
1470
+ }
1471
+ }
1472
+ ```
1473
+ ### @Prop与@ObjectLink的差异
1474
+ @Prop和@ObjectLink都可以接收@Observed装饰的类对象实例。@Prop对对象进行深拷贝,修改深拷贝后的对象不会影响原对象及其关联的组件。@ObjectLink获取对象的引用,修改引用对象会影响原对象及其关联的组件。
1475
+
1476
+ 下面的例子中,UserChild组件同时使用@Prop与@ObjectLink接收了来自父组件的@Observed装饰的类对象实例作为数据源。对该数据源对象的修改将同时影响@Prop与@ObjectLink装饰的变量。依次点击change @ObjectLink value按钮和change @Prop value按钮可以观察到:
1477
+
1478
+ - 修改@ObjectLink装饰的对象内容将影响数据源对象,并重新同步给@Prop,因此两个Text组件都将刷新。
1479
+ - 修改@Prop装饰的对象内容仅影响使用该对象的Text2组件,不会影响数据源对象。
1480
+ ```TypeScript
1481
+ let nextId = 0;
1482
+
1483
+ @Observed
1484
+ class User {
1485
+ public id: number;
1486
+
1487
+ constructor() {
1488
+ this.id = nextId++;
1489
+ }
1490
+ }
1491
+
1492
+ @Entry
1493
+ @Component
1494
+ struct Index {
1495
+ @State users: User[] = [new User(), new User(), new User()];
1496
+
1497
+ build() {
1498
+ Column() {
1499
+ UserChild({ firstUserByObjectLink: this.users[0], firstUserByProp: this.users[0] })
1500
+ }
1501
+ }
1502
+ }
1503
+
1504
+ @Component
1505
+ struct UserChild {
1506
+ @ObjectLink firstUserByObjectLink: User;
1507
+ @Prop firstUserByProp: User;
1508
+
1509
+ build() {
1510
+ Column() {
1511
+ // 比较结果为false说明@Prop经过深拷贝后得到的对象与原对象已不是同一个对象
1512
+ Text(`firstUserByObjectLink equals firstUserByProp? : ${this.firstUserByObjectLink === this.firstUserByProp}`)
1513
+ Text(`UserChild firstUserByObjectLink.id: ${this.firstUserByObjectLink.id}`) // Text1
1514
+ Text(`UserChild firstUserByProp.id: ${this.firstUserByProp.id}`) // Text2
1515
+ Button('change @ObjectLink value')
1516
+ .onClick(() => {
1517
+ this.firstUserByObjectLink.id++;
1518
+ })
1519
+ Button('change @Prop value')
1520
+ .onClick(() => {
1521
+ this.firstUserByProp.id++;
1522
+ })
1523
+ }
1524
+ }
1525
+ }
1526
+ ```
1527
+
1528
+ 上面的示例关系如图所示:
1529
+
1530
+ ![]((https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/cd/v3/ZuLt1CElQ1KSSCrAiIOKiw/zh-cn_image_0000002566867925.jpg)
1531
+ ### 在@Observed装饰类的构造函数中延时更改成员变量
1532
+ 在状态管理中,使用@Observed装饰类后,会给该类使用一层“代理”进行包装。当在组件中改变该类的成员变量时,会被该代理进行拦截,在更改数据源中值的同时,也会将变化通知给绑定的组件,从而实现观测变化与触发刷新。
1533
+
1534
+ 当开发者在类的构造函数中对成员变量进行赋值或者修改时,此修改不会经过代理(因为是直接对数据源中的值进行修改),也就无法被观测到。所以,如果开发者在类的构造函数中使用定时器修改类中的成员变量,即使该修改成功执行了,也不会触发UI的刷新。
1535
+
1536
+ 【反例】
1537
+
1538
+ ```ts
1539
+ @Observed
1540
+ class RenderClass {
1541
+ waitToRender: boolean = false;
1542
+
1543
+ constructor() {
1544
+ setTimeout(() => {
1545
+ this.waitToRender = true;
1546
+ console.info('更改waitToRender的值为:' + this.waitToRender);
1547
+ }, 1000)
1548
+ }
1549
+ }
1550
+
1551
+ @Entry
1552
+ @Component
1553
+ struct Index {
1554
+ @State @Watch('renderClassChange') renderClass: RenderClass = new RenderClass();
1555
+ @State textColor: Color = Color.Black;
1556
+
1557
+ renderClassChange() {
1558
+ console.info('renderClass的值被更改为:' + this.renderClass.waitToRender);
1559
+ }
1560
+
1561
+ build() {
1562
+ Row() {
1563
+ Column() {
1564
+ Text('renderClass的值为:' + this.renderClass.waitToRender)
1565
+ .fontSize(20)
1566
+ .fontColor(this.textColor)
1567
+ Button('Show')
1568
+ .onClick(() => {
1569
+ // 使用其他状态变量强行刷新UI的做法并不推荐,此处仅用来检测waitToRender的值是否更新
1570
+ this.textColor = Color.Red;
1571
+ })
1572
+ }
1573
+ .width('100%')
1574
+ }
1575
+ .height('100%')
1576
+ }
1577
+ }
1578
+ ```
1579
+
1580
+ 上文的示例代码中在RenderClass的构造函数中使用定时器在1秒后修改了waitToRender的值,但是不会触发UI的刷新。此时,点击按钮强行刷新Text组件,可以看到waitToRender的值已经被修改成了true。
1581
+
1582
+ 【正例】
1583
+
1584
+ ```TypeScript
1585
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1586
+
1587
+ const DOMAIN = 0x0001;
1588
+ const TAG = 'ArkTSObservedAndObjectlink';
1589
+
1590
+ @Observed
1591
+ class RenderClass {
1592
+ public waitToRender: boolean = false;
1593
+
1594
+ constructor() {
1595
+ }
1596
+ }
1597
+
1598
+ @Entry
1599
+ @Component
1600
+ struct DelayedChangeIndex {
1601
+ @State @Watch('renderClassChange') renderClass: RenderClass = new RenderClass();
1602
+
1603
+ renderClassChange() {
1604
+ hilog.info(DOMAIN, TAG, `The value of renderClass is changed to: ${this.renderClass.waitToRender}`);
1605
+ }
1606
+
1607
+ onPageShow() {
1608
+ setTimeout(() => {
1609
+ this.renderClass.waitToRender = true;
1610
+ }, 1000);
1611
+ }
1612
+
1613
+ build() {
1614
+ Row() {
1615
+ Column() {
1616
+ Text(`The value of renderClass is: ${this.renderClass.waitToRender}`)
1617
+ .fontSize(20)
1618
+ }
1619
+ .width('100%')
1620
+ }
1621
+ .height('100%')
1622
+ }
1623
+ }
1624
+ ```
1625
+
1626
+ 上文的示例代码将定时器修改移入到组件内,此时界面显示时会先显示“The value of renderClass is:false”。待定时器触发时,renderClass的值改变,触发[@Watch](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-watch)回调,此时界面刷新显示“The value of renderClass is:true”,日志输出“The value of renderClass is changed to:true”。
1627
+
1628
+ 因此,更推荐开发者在组件中对@Observed装饰的类成员变量进行修改,以实现刷新。
1629
+ ### @ObjectLink数据源更新时机
1630
+ ```TypeScript
1631
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1632
+
1633
+ const DOMAIN = 0x0001;
1634
+ const TAG = 'ArkTSObservedAndObjectlink';
1635
+
1636
+ @Observed
1637
+ class Person {
1638
+ public name: string = '';
1639
+ public age: number = 0;
1640
+
1641
+ constructor(name: string, age: number) {
1642
+ this.name = name;
1643
+ this.age = age;
1644
+ }
1645
+ }
1646
+
1647
+ @Observed
1648
+ class Info {
1649
+ public person: Person;
1650
+
1651
+ constructor(person: Person) {
1652
+ this.person = person;
1653
+ }
1654
+ }
1655
+
1656
+ @Entry
1657
+ @Component
1658
+ struct Parent {
1659
+ @State @Watch('onChange01') info: Info =
1660
+ new Info(
1661
+ new Person('Bob', 10)
1662
+ );
1663
+
1664
+ onChange01() {
1665
+ hilog.info(DOMAIN, TAG, `:::onChange01: + ${this.info.person.name}`); // 2
1666
+ }
1667
+
1668
+ build() {
1669
+ Column() {
1670
+ Text(this.info.person.name).height(40)
1671
+ Child({
1672
+ per: this.info.person, clickEvent: () => {
1673
+ hilog.info(DOMAIN, TAG, `:::clickEvent before ${this.info.person.name}`); // 1
1674
+ this.info.person = new Person('Jack', 12);
1675
+ hilog.info(DOMAIN, TAG, `:::clickEvent after ${this.info.person.name}`); // 3
1676
+ }
1677
+ })
1678
+ }
1679
+ }
1680
+ }
1681
+
1682
+ @Component
1683
+ struct Child {
1684
+ @ObjectLink @Watch('onChange02') per: Person;
1685
+ clickEvent?: () => void;
1686
+
1687
+ onChange02() {
1688
+ hilog.info(DOMAIN, TAG, `:::onChange02:${this.per.name}`); // 5
1689
+ }
1690
+
1691
+ build() {
1692
+ Column() {
1693
+ Button(this.per.name)
1694
+ .height(40)
1695
+ .onClick(() => {
1696
+ this.onClickType();
1697
+ })
1698
+ }
1699
+ }
1700
+
1701
+ private onClickType() {
1702
+ if (this.clickEvent) {
1703
+ this.clickEvent();
1704
+ }
1705
+ hilog.info(DOMAIN, TAG, `:::--------this.per.name in Child is still: ${this.per.name}`); // 4
1706
+ };
1707
+ }
1708
+ ```
1709
+
1710
+ @ObjectLink的数据源更新依赖其父组件,当父组件中数据源改变引起父组件刷新时,会重新设置子组件@ObjectLink的数据源。这个过程不是在父组件数据源变化后立刻发生的,而是在父组件实际刷新时才会进行。上述示例中,Parent包含Child,Parent传递箭头函数给Child,在点击时,日志打印顺序是1-2-3-4-5,打印到日志4时,点击事件流程结束,此时仅仅是将子组件Child标记为需要父组件更新的节点,因此日志4打印的this.per.name的值仍为Bob,等到父组件真正更新时,才会更新Child的数据源。
1711
+
1712
+ 当@ObjectLink @Watch('onChange02') per: Person的@Watch函数执行时,说明@ObjectLink的数据源已被父组件更新,此时日志5打印的值为更新后的Jack。
1713
+
1714
+ 日志的含义为:
1715
+
1716
+ - 日志1:对Parent @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10)) 赋值前。
1717
+
1718
+ - 日志2:对Parent @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10)) 赋值,执行其@Watch函数,同步执行。
1719
+
1720
+ - 日志3:对Parent @State @Watch('onChange01') info: Info = new Info(new Person('Bob', 10)) 赋值完成。
1721
+
1722
+ - 日志4:onClickType方法内clickEvent执行完,此时只是将子组件Child标记为需要父组件更新的节点,未将最新的值更新给Child @ObjectLink @Watch('onChange02') per: Person,所以日志4打印的this.per.name的值仍然是Bob。
1723
+
1724
+ - 日志5:下一次vsync信号触发Child更新,@ObjectLink @Watch('onChange02') per: Person被更新,触发其@Watch方法,此时@ObjectLink @Watch('onChange02') per: Person为新值Jack。
1725
+
1726
+ @Prop父子同步原理与@ObjectLink一致。
1727
+
1728
+ 当clickEvent中更改this.info.person.name时,修改会立刻生效,此时日志4打印的值是Jack。
1729
+
1730
+ ```TypeScript
1731
+ Child({
1732
+ per: this.info.person, clickEvent: () => {
1733
+ hilog.info(DOMAIN, TAG, `:::clickEvent before ${this.info.person.name}`); // 1
1734
+ this.info.person.name = 'Jack';
1735
+ hilog.info(DOMAIN, TAG, `:::clickEvent after ${this.info.person.name}`); // 3
1736
+ }
1737
+ })
1738
+ ```
1739
+
1740
+ 此时Parent中Text组件不会刷新,因为this.info.person.name属于两层嵌套。
1741
+ ### @Observed装饰的类,在构造函数中使用this赋值属性,不触发UI更新
1742
+ @Observed类的构造函数中对成员变量进行赋值或者修改时,此修改不会经过代理,无法被观测到。
1743
+
1744
+ 【反例】
1745
+
1746
+ ```ts
1747
+ @Observed
1748
+ class DataDownloader {
1749
+ state: number;
1750
+ constructor() {
1751
+ this.state = 0;
1752
+ setInterval(() => {
1753
+ // 从构造函数修改成员变量,不触发UI更新
1754
+ this.state += 1;
1755
+ }, 2000);
1756
+ }
1757
+ }
1758
+
1759
+ @Entry
1760
+ @Component
1761
+ struct Index {
1762
+ @State dataDownloader: DataDownloader = new DataDownloader();
1763
+ build() {
1764
+ Column() {
1765
+ Text(`Download state is ${this.dataDownloader.state}`)
1766
+ }
1767
+ }
1768
+ }
1769
+ ```
1770
+
1771
+ 【正例】
1772
+
1773
+ ```TypeScript
1774
+ @Observed
1775
+ class DataDownloader {
1776
+ public state: number;
1777
+
1778
+ constructor() {
1779
+ this.state = 0;
1780
+ }
1781
+
1782
+ startIntervalUpdate() {
1783
+ setInterval(() => {
1784
+ this.state += 1;
1785
+ }, 2000);
1786
+ }
1787
+ }
1788
+
1789
+ @Entry
1790
+ @Component
1791
+ struct Index {
1792
+ @State dataDownloader: DataDownloader = new DataDownloader();
1793
+
1794
+ aboutToAppear() {
1795
+ this.dataDownloader.startIntervalUpdate(); // @Observed装饰的类构建后再修改属性可以触发更新UI
1796
+ }
1797
+
1798
+ build() {
1799
+ Column() {
1800
+ Text(`Download state is ${this.dataDownloader.state}`)
1801
+ }
1802
+ }
1803
+ }
1804
+ ```
1805
+
1806
+ ![]((https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/c8/v3/oA_e8FS-QTuReMVE0ePZEw/zh-cn_image_0000002566707943.gif)
1807
+ ### LazyForEach和@ObjectLink一起使用时,替换数组数据后UI不刷新
1808
+ @Observed装饰的类的数组,用[LazyForEach](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-rendering-control-lazyforeach)展开显示的时候,可能会出现替换数组数据后,修改数组数据不刷新UI的问题。改变数组数据后,需要调用onDataChange通知LazyForEach组件重新绑定状态变量,否则就会出现上述问题。
1809
+
1810
+ 【反例】
1811
+
1812
+ ```ts
1813
+ // LazyForEach遍历数据基类
1814
+ class BasicDataSource implements IDataSource {
1815
+ private listeners: DataChangeListener[] = [];
1816
+ private originDataArray: StringData[] = [];
1817
+
1818
+ public totalCount(): number {
1819
+ return this.originDataArray.length;
1820
+ }
1821
+
1822
+ public getData(index: number): StringData {
1823
+ return this.originDataArray[index];
1824
+ }
1825
+
1826
+ registerDataChangeListener(listener: DataChangeListener): void {
1827
+ if (this.listeners.indexOf(listener) < 0) {
1828
+ console.info('add listener');
1829
+ this.listeners.push(listener);
1830
+ }
1831
+ }
1832
+
1833
+ unregisterDataChangeListener(listener: DataChangeListener): void {
1834
+ const pos = this.listeners.indexOf(listener);
1835
+ if (pos >= 0) {
1836
+ console.info('remove listener');
1837
+ this.listeners.splice(pos, 1);
1838
+ }
1839
+ }
1840
+
1841
+ notifyDataAdd(index: number): void {
1842
+ this.listeners.forEach(listener => {
1843
+ listener.onDataAdd(index);
1844
+ });
1845
+ }
1846
+ }
1847
+
1848
+ // LazyForEach遍历数据类型
1849
+ class MyDataSource extends BasicDataSource {
1850
+ public dataArray: StringData[] = [];
1851
+
1852
+ public totalCount(): number {
1853
+ return this.dataArray.length;
1854
+ }
1855
+
1856
+ public getData(index: number): StringData {
1857
+ return this.dataArray[index];
1858
+ }
1859
+
1860
+ public pushData(data: StringData): void {
1861
+ this.dataArray.push(data);
1862
+ this.notifyDataAdd(this.dataArray.length - 1);
1863
+ }
1864
+ }
1865
+
1866
+ @Observed
1867
+ class StringData {
1868
+ message: string;
1869
+
1870
+ constructor(message: string) {
1871
+ this.message = message;
1872
+ }
1873
+ }
1874
+
1875
+ @Entry
1876
+ @Component
1877
+ struct MyComponent {
1878
+ private data: MyDataSource = new MyDataSource();
1879
+ helloCount: number = 4;
1880
+
1881
+ aboutToAppear() {
1882
+ for (let i = 0; i <= 3; i++) {
1883
+ this.data.pushData(new StringData(`Hello ${i}`));
1884
+ }
1885
+ }
1886
+
1887
+ build() {
1888
+ Column() {
1889
+ List({ space: 3 }) {
1890
+ // 使用LazyForEach懒加载遍历数据
1891
+ LazyForEach(this.data, (item: StringData, index: number) => {
1892
+ ListItem() {
1893
+ ChildComponent({ data: item })
1894
+ }
1895
+ }, (item: StringData, index: number) => index.toString() + item.message)
1896
+ }.cachedCount(3)
1897
+ Button('替换第一个元素')
1898
+ .onClick(() => {
1899
+ // 替换数组元素不刷新UI,此时新替换的值还未绑定到LazyForEach组件上。
1900
+ this.data.dataArray[0] = new StringData('Hello ' + this.helloCount++)
1901
+ })
1902
+ Button('修改第一个元素的数据')
1903
+ .onClick(() => {
1904
+ // 替换数组元素后修改元素值也不会刷新UI。
1905
+ this.data.dataArray[0].message += '1';
1906
+ })
1907
+ }
1908
+ }
1909
+ }
1910
+
1911
+ // 使用@Reusable实现组件复用
1912
+ @Reusable
1913
+ @Component
1914
+ struct ChildComponent {
1915
+ // 使用@ObjectLink接收@Observed装饰的类的数据
1916
+ @ObjectLink data: StringData;
1917
+
1918
+ aboutToAppear(): void {
1919
+ console.info(`aboutToAppear: ${this.data.message}`);
1920
+ }
1921
+
1922
+ aboutToRecycle(): void {
1923
+ console.info(`aboutToRecycle: ${this.data.message}`);
1924
+ }
1925
+
1926
+ // 对复用的组件进行数据更新
1927
+ aboutToReuse(params: Record<string, ESObject>): void {
1928
+ this.data.message = (params.data as StringData).message;
1929
+ console.info(`aboutToReuse: ${this.data.message}`);
1930
+ }
1931
+
1932
+ build() {
1933
+ Row() {
1934
+ Text(this.data.message)
1935
+ .fontSize(50)
1936
+ .onAppear(() => {
1937
+ console.info(`appear: ${this.data.message}`);
1938
+ })
1939
+ }.margin({ left: 10, right: 10 })
1940
+ }
1941
+ }
1942
+ ```
1943
+
1944
+ 【正例】
1945
+
1946
+ ```TypeScript
1947
+ // LazyForEach遍历数据基类
1948
+ class BasicDataSource implements IDataSource {
1949
+ private listeners: DataChangeListener[] = [];
1950
+ private originDataArray: StringData[] = [];
1951
+
1952
+ public totalCount(): number {
1953
+ return this.originDataArray.length;
1954
+ }
1955
+
1956
+ public getData(index: number): StringData {
1957
+ return this.originDataArray[index];
1958
+ }
1959
+
1960
+ registerDataChangeListener(listener: DataChangeListener): void {
1961
+ if (this.listeners.indexOf(listener) < 0) {
1962
+ console.info('add listener');
1963
+ this.listeners.push(listener);
1964
+ }
1965
+ }
1966
+
1967
+ unregisterDataChangeListener(listener: DataChangeListener): void {
1968
+ const pos = this.listeners.indexOf(listener);
1969
+ if (pos >= 0) {
1970
+ console.info('remove listener');
1971
+ this.listeners.splice(pos, 1);
1972
+ }
1973
+ }
1974
+
1975
+ notifyDataAdd(index: number): void {
1976
+ this.listeners.forEach(listener => {
1977
+ listener.onDataAdd(index);
1978
+ });
1979
+ }
1980
+
1981
+ // 通知LazyForEach处理数据替换
1982
+ notifyDataChanged(index: number): void {
1983
+ this.listeners.forEach(listener => {
1984
+ listener.onDataChange(index);
1985
+ })
1986
+ }
1987
+ }
1988
+
1989
+ // LazyForEach遍历数据类型
1990
+ class MyDataSource extends BasicDataSource {
1991
+ public dataArray: StringData[] = [];
1992
+
1993
+ public totalCount(): number {
1994
+ return this.dataArray.length;
1995
+ }
1996
+
1997
+ public getData(index: number): StringData {
1998
+ return this.dataArray[index];
1999
+ }
2000
+
2001
+ public pushData(data: StringData): void {
2002
+ this.dataArray.push(data);
2003
+ this.notifyDataAdd(this.dataArray.length - 1);
2004
+ }
2005
+ }
2006
+
2007
+ @Observed
2008
+ class StringData {
2009
+ public message: string;
2010
+
2011
+ constructor(message: string) {
2012
+ this.message = message;
2013
+ }
2014
+ }
2015
+
2016
+ @Entry
2017
+ @Component
2018
+ struct MyComponent {
2019
+ private data: MyDataSource = new MyDataSource();
2020
+ helloCount: number = 4;
2021
+
2022
+ aboutToAppear() {
2023
+ for (let i = 0; i <= 2; i++) {
2024
+ this.data.pushData(new StringData(`Hello ${i}`));
2025
+ }
2026
+ }
2027
+
2028
+ build() {
2029
+ Column({ space: 3 }) {
2030
+ List({ space: 3 }) {
2031
+ // 使用LazyForEach懒加载遍历数据
2032
+ LazyForEach(this.data, (item: StringData, index: number) => {
2033
+ ListItem() {
2034
+ ChildComponent({ data: item })
2035
+ }.width('100%')
2036
+ // LazyForEach的key从index和message构建,每次替换元素时,需要修改key才能触发UI刷新。
2037
+ }, (item: StringData, index: number) => index.toString() + item.message)
2038
+ }.cachedCount(3)
2039
+ Button('替换第一个元素')
2040
+ .onClick(() => {
2041
+ this.data.dataArray[0] = new StringData('Hello ' + this.helloCount++);
2042
+ // 替换元素后通知LazyForEach,可以刷新UI。
2043
+ this.data.notifyDataChanged(0);
2044
+ })
2045
+ Button('修改第一个元素的数据')
2046
+ .onClick(() => {
2047
+ // 替换元素后由于重新建立绑定,后续修改元素值也能刷新UI。
2048
+ this.data.dataArray[0].message += '1';
2049
+ })
2050
+ }
2051
+ .width('100%')
2052
+ .alignItems(HorizontalAlign.Center)
2053
+ }
2054
+ }
2055
+
2056
+ // 使用Reusable使能组件复用
2057
+ @Reusable
2058
+ @Component
2059
+ struct ChildComponent {
2060
+ // 使用@ObjectLink接受@Observed类数据
2061
+ @ObjectLink data: StringData;
2062
+
2063
+ aboutToAppear(): void {
2064
+ console.info(`aboutToAppear: ${this.data.message}`);
2065
+ }
2066
+
2067
+ aboutToRecycle(): void {
2068
+ console.info(`aboutToRecycle: ${this.data.message}`);
2069
+ }
2070
+
2071
+ // 对复用的组件进行数据更新
2072
+ aboutToReuse(params: Record<string, ESObject>): void {
2073
+ this.data.message = (params.data as StringData).message;
2074
+ console.info(`aboutToReuse: ${this.data.message}`);
2075
+ }
2076
+
2077
+ build() {
2078
+ Row() {
2079
+ Text(this.data.message)
2080
+ .fontSize(50)
2081
+ .onAppear(() => {
2082
+ console.info(`appear: ${this.data.message}`);
2083
+ })
2084
+ }.margin({ left: 10, right: 10 })
2085
+ }
2086
+ }
2087
+ ```
2088
+
2089
+ ![]((https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/54/v3/6KBQjIO-SxOMBiU_dZMMHQ/zh-cn_image_0000002535788148.gif)