android2harmony 0.1.2 → 0.1.4

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 (47) hide show
  1. package/agents/self-tester.md +376 -0
  2. package/dist/index.js +152 -7
  3. package/dist/index.js.map +4 -4
  4. package/package.json +32 -29
  5. package/skills/hmos-incremental-ui-align/README.md +251 -0
  6. package/skills/hmos-incremental-ui-align/SKILL.md +365 -0
  7. package/skills/hmos-incremental-ui-align/diff_analysis.md +53 -0
  8. package/skills/hmos-incremental-ui-align/page_align.md +62 -0
  9. package/skills/hmos-incremental-ui-align/references/Comparison_Template.md +38 -0
  10. package/skills/hmos-incremental-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
  11. package/skills/hmos-incremental-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
  12. package/skills/hmos-incremental-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
  13. package/skills/hmos-incremental-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
  14. package/skills/hmos-incremental-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
  15. package/skills/hmos-incremental-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
  16. package/skills/hmos-incremental-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
  17. package/skills/hmos-incremental-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
  18. package/skills/hmos-incremental-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
  19. package/skills/hmos-incremental-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
  20. package/skills/hmos-incremental-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
  21. package/skills/hmos-incremental-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
  22. package/skills/hmos-incremental-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
  23. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md +206 -0
  24. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md +373 -0
  25. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md +158 -0
  26. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md +750 -0
  27. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md +1704 -0
  28. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md +1012 -0
  29. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Once/350/243/205/351/245/260/345/231/250/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md +164 -0
  30. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Param/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md +840 -0
  31. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md +856 -0
  32. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md +83 -0
  33. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md +294 -0
  34. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md +1407 -0
  35. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/PersistenceV2/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 +1220 -0
  36. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +47 -0
  37. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/220/221V2/350/277/201/347/247/273/344/270/216/346/267/267/347/224/250/346/214/207/345/257/274.md +840 -0
  38. package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +74 -0
  39. package/skills/hmos-incremental-ui-align/references/UI_Analysis_Template.md +34 -0
  40. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2533 -0
  41. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
  42. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
  43. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +999 -0
  44. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +343 -0
  45. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -0
  46. package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +977 -0
  47. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +188 -0
@@ -0,0 +1,1012 @@
1
+ # @ObservedV2装饰器和@Trace装饰器:类属性变化观测
2
+ 为了增强状态管理框架对类对象中属性的观测能力,开发者可以使用@ObservedV2装饰器和@Trace装饰器装饰类以及类中的属性。
3
+ @ObservedV2和@Trace提供了对嵌套类对象属性变化直接观测的能力,是状态管理V2中相对核心的能力之一。在阅读本文档前,建议提前阅读: [状态管理概述](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management-overview) 来了解状态管理V2整体的能力架构。
4
+ ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/ab/v3/bX6jzM2cSFKCjTby-YTy7w/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260701T104555Z&HW-CC-Expire=86400&HW-CC-Sign=F9C7E2DFC41B3E89AD210BDA3B83CBB24D4A59A252428E839B40CD3B7D94C50E)
5
+ @ObservedV2与@Trace装饰器从API version 12开始支持。
6
+ 从API version 12开始,@ObservedV2与@Trace装饰器支持在ArkTS卡片中使用。
7
+ 从API version 12开始,@ObservedV2与@Trace装饰器支持在元服务中使用。
8
+ #### 概述
9
+ @ObservedV2装饰器与@Trace装饰器用于装饰类以及类中的属性,使得被装饰的类和属性具有深度观测的能力:
10
+ - @ObservedV2装饰器与@Trace装饰器需要配合使用,单独使用@ObservedV2装饰器或@Trace装饰器没有任何作用。
11
+ - 被@Trace装饰器装饰的属性property变化时,仅会通知property关联的组件进行刷新。
12
+ - 在嵌套类中,嵌套类中的属性property被@Trace装饰且嵌套类被@ObservedV2装饰时,才具有触发UI刷新的能力。
13
+ - 在继承类中,父类或子类中的属性property被@Trace装饰且该property所在类被@ObservedV2装饰时,才具有触发UI刷新的能力。
14
+ - 未被@Trace装饰的属性用在UI中无法感知到变化,也无法触发UI刷新。
15
+ - 使用@ObservedV2与@Trace装饰器的类,需通过new操作符实例化后,才具备被观测变化的能力。
16
+ #### 状态管理V1版本对嵌套类对象属性变化直接观测的局限性
17
+ 现有状态管理V1版本无法实现对嵌套类对象属性变化的直接观测。
18
+ ```
19
+ @Observed
20
+ class Father {
21
+ public son: Son;
22
+ constructor(name: string, age: number) {
23
+ this.son = new Son(name, age);
24
+ }
25
+ }
26
+ @Observed
27
+ class Son {
28
+ public name: string;
29
+ public age: number;
30
+ constructor(name: string, age: number) {
31
+ this.name = name;
32
+ this.age = age;
33
+ }
34
+ }
35
+ @Entry
36
+ @Component
37
+ struct Index {
38
+ @State father: Father = new Father('John', 8);
39
+ build() {
40
+ Row() {
41
+ Column() {
42
+ Text(`name: ${this.father.son.name} age: ${this.father.son.age}`)
43
+ .fontSize(50)
44
+ .fontWeight(FontWeight.Bold)
45
+ .onClick(() => {
46
+ // 嵌套类对象属性变化无法观测
47
+ this.father.son.age++;
48
+ })
49
+ }
50
+ .width('100%')
51
+ }
52
+ .height('100%')
53
+ }
54
+ }
55
+ ```
56
+ 在上述代码中,点击Text组件增加age的值时,不会触发UI刷新。原因在于现有的状态管理框架无法观测到嵌套类中属性age的值变化。V1版本的解决方案是使用 [@ObjectLink装饰器](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-observed-and-objectlink) 与自定义组件来实现观测。
57
+ ```
58
+ @Observed
59
+ class Father {
60
+ public son: Son;
61
+ constructor(name: string, age: number) {
62
+ this.son = new Son(name, age);
63
+ }
64
+ }
65
+ @Observed
66
+ class Son {
67
+ public name: string;
68
+ public age: number;
69
+ constructor(name: string, age: number) {
70
+ this.name = name;
71
+ this.age = age;
72
+ }
73
+ }
74
+ @Component
75
+ struct Child {
76
+ // @Observed对象与@ObjectLink一起使用,实现对嵌套类对象属性的观测能力
77
+ @ObjectLink son: Son;
78
+ build() {
79
+ Row() {
80
+ Column() {
81
+ Text(`name: ${this.son.name} age: ${this.son.age}`)
82
+ .fontSize(50)
83
+ .fontWeight(FontWeight.Bold)
84
+ .onClick(() => {
85
+ this.son.age++;
86
+ })
87
+ }
88
+ .width('100%')
89
+ }
90
+ .height('100%')
91
+ }
92
+ }
93
+ @Entry
94
+ @Component
95
+ struct Index {
96
+ @State father: Father = new Father('John', 8);
97
+ build() {
98
+ Column() {
99
+ Child({ son: this.father.son })
100
+ }
101
+ }
102
+ }
103
+ ```
104
+ 通过这种方式虽然能够实现对嵌套类中属性变化的观测,但是当嵌套层级较深时,代码将会变得十分复杂,易用性差。因此推出类装饰器@ObservedV2与成员变量装饰器@Trace,增强对嵌套类中属性变化的观测能力。
105
+ #### 装饰器说明
106
+ | @ObservedV2类装饰器 | 说明 |
107
+ | --- | --- |
108
+ | 装饰器参数 | 无。 |
109
+ | 类装饰器 | 装饰class。需要放在class的定义前,使用new创建类对象。 |
110
+ | @Trace成员变量装饰器 | 说明 |
111
+ | --- | --- |
112
+ | 装饰器参数 | 无。 |
113
+ | 可装饰的变量 | class中成员属性。属性的类型可以为number、string、boolean、class、[Array](#trace装饰基础类型的数组)、[Date](#trace装饰date类型)、[Map](#trace装饰map类型)、[Set](#trace装饰set类型)等类型。@Trace不支持观察Function类型的数据,修改@Trace装饰的Function类型的数据,UI不会刷新。 |
114
+ #### 观察变化
115
+ 使用@ObservedV2装饰的类中被@Trace装饰的属性具有被观测变化的能力,当该属性值变化时,会触发该属性绑定的UI组件刷新。
116
+ - 在嵌套类中使用@Trace装饰的属性具有被观测变化的能力。
117
+ ```
118
+ @ObservedV2
119
+ class Son {
120
+ @Trace public age: number = 100;
121
+ }
122
+ class Father {
123
+ public son: Son = new Son();
124
+ }
125
+ @Entry
126
+ @ComponentV2
127
+ struct Index {
128
+ father: Father = new Father();
129
+ build() {
130
+ Column() {
131
+ // 当点击改变age时,Text组件会刷新
132
+ Text(`${this.father.son.age}`)
133
+ .onClick(() => {
134
+ this.father.son.age++;
135
+ })
136
+ }
137
+ }
138
+ }
139
+ ```
140
+ - 在继承类中使用@Trace装饰的属性具有被观测变化的能力。
141
+ ```
142
+ @ObservedV2
143
+ class Father {
144
+ @Trace public name: string = 'Tom';
145
+ }
146
+ class Son extends Father {
147
+ }
148
+ @Entry
149
+ @ComponentV2
150
+ struct Index {
151
+ son: Son = new Son();
152
+ build() {
153
+ Column() {
154
+ // 当点击改变name时,Text组件会刷新
155
+ Text(`${this.son.name}`)
156
+ .onClick(() => {
157
+ this.son.name = 'Jack';
158
+ })
159
+ }
160
+ }
161
+ }
162
+ ```
163
+ - 类中使用@Trace装饰的静态属性具有被观测变化的能力。
164
+ ```
165
+ @ObservedV2
166
+ class Manager {
167
+ @Trace public static count: number = 1;
168
+ }
169
+ @Entry
170
+ @ComponentV2
171
+ struct Index {
172
+ build() {
173
+ Column() {
174
+ // 当点击改变count时,Text组件会刷新
175
+ Text(`${Manager.count}`)
176
+ .onClick(() => {
177
+ Manager.count++;
178
+ })
179
+ }
180
+ }
181
+ }
182
+ ```
183
+ -
184
+ @Trace装饰内置类型时,可以观测各自API导致的变化:
185
+ | 类型 | 可观测变化的API |
186
+ | --- | --- |
187
+ | Array | push、pop、shift、unshift、splice、copyWithin、fill、reverse、sort |
188
+ | Date | setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds |
189
+ | Map | set, clear, delete |
190
+ | Set | add, clear, delete |
191
+ #### 使用限制
192
+ @ObservedV2与@Trace装饰器存在以下使用限制:
193
+ - 非@Trace装饰的成员属性用在UI上无法触发UI刷新。
194
+ ```
195
+ @ObservedV2
196
+ class Person {
197
+ public id: number = 0;
198
+ @Trace public age: number = 8;
199
+ }
200
+ @Entry
201
+ @ComponentV2
202
+ struct Index {
203
+ person: Person = new Person();
204
+ build() {
205
+ Column() {
206
+ // age被@Trace装饰,用在UI中可以触发UI刷新
207
+ Text(`${this.person.age}`)
208
+ .onClick(() => {
209
+ this.person.age++; // 点击会触发UI刷新
210
+ })
211
+ // id未被@Trace装饰,用在UI中不会触发UI刷新
212
+ Text(`${this.person.id}`) // 当id变化时不会刷新
213
+ .onClick(() => {
214
+ this.person.id++; // 点击不会触发UI刷新
215
+ })
216
+ }
217
+ }
218
+ }
219
+ ```
220
+ - @ObservedV2仅能装饰class,无法装饰自定义组件。
221
+ ```
222
+ @ObservedV2 // 错误用法,编译时报错
223
+ struct Index {
224
+ build() {
225
+ }
226
+ }
227
+ ```
228
+ - @Trace不能用在没有被@ObservedV2装饰的class上。
229
+ ```
230
+ class User {
231
+ id: number = 0;
232
+ @Trace name: string = 'Tom'; // 错误用法,编译时报错
233
+ }
234
+ ```
235
+ - @Trace是class中属性的装饰器,不能用在struct中。
236
+ ```
237
+ @ComponentV2
238
+ struct Comp {
239
+ @Trace message: string = 'Hello World'; // 错误用法,编译时报错
240
+ build() {
241
+ }
242
+ }
243
+ ```
244
+ - @ObservedV2、@Trace不能与[@Observed](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-observed-and-objectlink)、[@Track](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-track)混合使用。
245
+ ```
246
+ @Observed
247
+ class User {
248
+ @Trace name: string = 'Tom'; // 错误用法,编译时报错
249
+ }
250
+ @ObservedV2
251
+ class Person {
252
+ @Track name: string = 'Jack'; // 错误用法,编译时报错
253
+ }
254
+ ```
255
+ - 使用@ObservedV2与@Trace装饰的类不能和[@State](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state)等V1的装饰器混合使用,编译时报错。
256
+ ```
257
+ // 以@State装饰器为例
258
+ @ObservedV2
259
+ class Job {
260
+ @Trace public jobName: string = 'Teacher';
261
+ }
262
+ @ObservedV2
263
+ class Info {
264
+ @Trace public name: string = 'Tom';
265
+ @Trace public age: number = 25;
266
+ public job: Job = new Job();
267
+ }
268
+ @Entry
269
+ @ComponentV2
270
+ struct Index {
271
+ // @State info: Info = new Info(); 无法混用,编译时报错
272
+ @Local info: Info = new Info();
273
+ build() {
274
+ Column() {
275
+ Text(`name: ${this.info.name}`)
276
+ Text(`age: ${this.info.age}`)
277
+ Text(`jobName: ${this.info.job.jobName}`)
278
+ Button('change age')
279
+ .onClick(() => {
280
+ this.info.age++;
281
+ })
282
+ Button('Change job')
283
+ .onClick(() => {
284
+ this.info.job.jobName = 'Doctor';
285
+ })
286
+ }
287
+ }
288
+ }
289
+ ```
290
+ - 继承自@ObservedV2的类无法和@State等V1的装饰器混用,运行时报错。
291
+ ```
292
+ // 以@State装饰器为例
293
+ @ObservedV2
294
+ class Job {
295
+ @Trace public jobName: string = 'Teacher';
296
+ }
297
+ @ObservedV2
298
+ class Info {
299
+ @Trace public name: string = 'Tom';
300
+ @Trace public age: number = 25;
301
+ public job: Job = new Job();
302
+ }
303
+ class Message extends Info {
304
+ constructor() {
305
+ super();
306
+ }
307
+ }
308
+ @Entry
309
+ @Component
310
+ struct Index {
311
+ // @State message: Message = new Message(); 无法混用,运行时报错
312
+ message: Message = new Message();
313
+ build() {
314
+ Column() {
315
+ Text(`name: ${this.message.name}`)
316
+ Text(`age: ${this.message.age}`)
317
+ Text(`jobName: ${this.message.job.jobName}`)
318
+ Button('change age')
319
+ .onClick(() => {
320
+ this.message.age++;
321
+ })
322
+ Button('Change job')
323
+ .onClick(() => {
324
+ this.message.job.jobName = 'Doctor';
325
+ })
326
+ }
327
+ }
328
+ }
329
+ ```
330
+ - 使用@ObservedV2与@Trace装饰器的类,需通过new操作符实例化后,才具备被观测变化的能力。
331
+ - @ObservedV2的类实例无法直接使用JSON.parse反序列化获得(直接使用JSON.parse反序列化获得的对象无法观察属性变化),可搭配三方库[class-transformer](https://gitcode.com/openharmony-tpc/openharmony_tpc_samples/tree/master/class-transformer)实现反序列化后可观察,示例请参考[@ObservedV2装饰对象的序列化与反序列化](#observedv2装饰对象的序列化与反序列化)。
332
+ #### 使用场景
333
+ #### 嵌套类场景
334
+ 在下面的嵌套类场景中,Pencil类是Son类中最里层的类,Pencil类被@ObservedV2装饰且属性length被@Trace装饰,此时length的变化能够被观测到。
335
+ @Trace装饰器与现有状态管理框架的 [@Track](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-track) 与 [@State](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state) 装饰器的能力不同,@Track使class具有属性级更新的能力,但并不具备深度观测的能力;而@State只能观测到对象本身以及第一层的变化,对于多层嵌套场景只能通过封装自定义组件,搭配 [@Observed](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-observed-and-objectlink) 和 [@ObjectLink](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-observed-and-objectlink) 来实现观测。
336
+ - 点击Button('change length'),length是被@Trace装饰的属性,它的变化可以触发关联的UI组件,即UINode (1)的刷新,并输出"id: 1 renderTimes: x"的日志,其中x根据点击次数依次增长。
337
+ - 自定义组件Page中的son是常规变量,因此点击Button('assign Son')并不会观测到变化。
338
+ - 当点击Button('assign Son')后,再点击Button('change length')并不会引起UI刷新。因为此时son的地址改变,其关联的UI组件并没有关联到最新的son。
339
+ ```
340
+ import { hilog } from '@kit.PerformanceAnalysisKit';
341
+ const DOMAIN = 0x0001;
342
+ const TAG = 'ArktsObservedV2AndTrace';
343
+ @ObservedV2
344
+ class Pencil {
345
+ @Trace public length: number = 21; // 当length变化时,会刷新关联的组件
346
+ }
347
+ class Bag {
348
+ public width: number = 50;
349
+ public height: number = 60;
350
+ public pencil: Pencil = new Pencil();
351
+ }
352
+ class Son {
353
+ public age: number = 5;
354
+ public school: string = 'some';
355
+ public bag: Bag = new Bag();
356
+ }
357
+ @Entry
358
+ @ComponentV2
359
+ struct Page {
360
+ son: Son = new Son();
361
+ renderTimes: number = 0;
362
+ isRender(id: number): number {
363
+ hilog.info(DOMAIN, TAG, `id: ${id} renderTimes: ${this.renderTimes}`);
364
+ this.renderTimes++;
365
+ return 40;
366
+ }
367
+ build() {
368
+ Column() {
369
+ Text('pencil length' + this.son.bag.pencil.length)
370
+ .fontSize(this.isRender(1)) // UINode (1)
371
+ Button('change length')
372
+ .onClick(() => {
373
+ // 点击更改length值,UINode(1)会刷新
374
+ this.son.bag.pencil.length += 100;
375
+ })
376
+ Button('assign Son')
377
+ .onClick(() => {
378
+ // 由于变量son非状态变量,因此无法刷新UINode(1)
379
+ this.son = new Son();
380
+ })
381
+ }
382
+ }
383
+ }
384
+ ```
385
+ #### 继承类场景
386
+ @Trace支持在类的继承场景中使用,无论是在基类还是继承类中,只有被@Trace装饰的属性才具有被观测变化的能力。
387
+ 以下例子中,声明class GrandFather、Father、Uncle、Son、Cousin,继承关系如下图。
388
+ ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/11/v3/tn0lMfacRg2uZn15TlMXJw/zh-cn_image_0000002628860106.png?HW-CC-KV=V1&HW-CC-Date=20260701T104555Z&HW-CC-Expire=86400&HW-CC-Sign=F50B07B9118AE157FB57AC1C6C6EE43A2EF5BB340CAE558A4275F269A4976593)
389
+ 创建类Son和类Cousin的实例,点击Button('change Son age')和Button('change Cousin age')可以触发UI的刷新。
390
+ ```
391
+ import { hilog } from '@kit.PerformanceAnalysisKit';
392
+ const DOMAIN = 0x0001;
393
+ const TAG = 'ArktsObservedV2AndTrace';
394
+ @ObservedV2
395
+ class GrandFather {
396
+ // 被@Trace装饰的属性具有被观测变化的能力
397
+ @Trace public age: number = 0;
398
+ constructor(age: number) {
399
+ this.age = age;
400
+ }
401
+ }
402
+ class Father extends GrandFather {
403
+ constructor(father: number) {
404
+ super(father);
405
+ }
406
+ }
407
+ class Uncle extends GrandFather {
408
+ constructor(uncle: number) {
409
+ super(uncle);
410
+ }
411
+ }
412
+ class Son extends Father {
413
+ constructor(son: number) {
414
+ super(son);
415
+ }
416
+ }
417
+ class Cousin extends Uncle {
418
+ constructor(cousin: number) {
419
+ super(cousin);
420
+ }
421
+ }
422
+ @Entry
423
+ @ComponentV2
424
+ struct Index {
425
+ son: Son = new Son(0);
426
+ cousin: Cousin = new Cousin(0);
427
+ renderTimes: number = 0;
428
+ isRender(id: number): number {
429
+ hilog.info(DOMAIN, TAG, `id: ${id} renderTimes: ${this.renderTimes}`);
430
+ this.renderTimes++;
431
+ return 40;
432
+ }
433
+ build() {
434
+ Row() {
435
+ Column() {
436
+ Text(`Son ${this.son.age}`)
437
+ .fontSize(this.isRender(1))
438
+ .fontWeight(FontWeight.Bold)
439
+ Text(`Cousin ${this.cousin.age}`)
440
+ .fontSize(this.isRender(2))
441
+ .fontWeight(FontWeight.Bold)
442
+ Button('change Son age')
443
+ .onClick(() => {
444
+ this.son.age++;
445
+ })
446
+ Button('change Cousin age')
447
+ .onClick(() => {
448
+ this.cousin.age++;
449
+ })
450
+ }
451
+ .width('100%')
452
+ }
453
+ .height('100%')
454
+ }
455
+ }
456
+ ```
457
+ #### @Trace装饰基础类型的数组
458
+ @Trace装饰数组时,使用支持的API能够观测到变化。支持的API见 [观察变化](#观察变化) 。
459
+ 在下面的示例中@ObservedV2装饰的Arr类中的属性numberArr是@Trace装饰的数组,当使用数组API操作numberArr时,可以观测到对应的变化。注意使用数组长度进行判断以防越界访问。
460
+ ```
461
+ let nextId: number = 0;
462
+ @ObservedV2
463
+ class Arr {
464
+ public id: number = 0;
465
+ @Trace public numberArr: number[] = [];
466
+ constructor() {
467
+ this.id = nextId++;
468
+ this.numberArr = [0, 1, 2];
469
+ }
470
+ }
471
+ @Entry
472
+ @ComponentV2
473
+ struct Index {
474
+ arr: Arr = new Arr();
475
+ build() {
476
+ Column() {
477
+ Text(`length: ${this.arr.numberArr.length}`)
478
+ .fontSize(40)
479
+ Divider()
480
+ if (this.arr.numberArr.length >= 3) {
481
+ Text(`${this.arr.numberArr[0]}`)
482
+ .fontSize(40)
483
+ .onClick(() => {
484
+ this.arr.numberArr[0]++;
485
+ })
486
+ Text(`${this.arr.numberArr[1]}`)
487
+ .fontSize(40)
488
+ .onClick(() => {
489
+ this.arr.numberArr[1]++;
490
+ })
491
+ Text(`${this.arr.numberArr[2]}`)
492
+ .fontSize(40)
493
+ .onClick(() => {
494
+ this.arr.numberArr[2]++;
495
+ })
496
+ }
497
+ Divider()
498
+ ForEach(this.arr.numberArr, (item: number, index: number) => {
499
+ Text(`${index} ${item}`)
500
+ .fontSize(40)
501
+ })
502
+ // numberArr是@Trace装饰的数组
503
+ // 使用数组API操作numberArr时,可以观测到对应的变化
504
+ Button('push')
505
+ .onClick(() => {
506
+ this.arr.numberArr.push(50);
507
+ })
508
+ Button('pop')
509
+ .onClick(() => {
510
+ this.arr.numberArr.pop();
511
+ })
512
+ Button('shift')
513
+ .onClick(() => {
514
+ this.arr.numberArr.shift();
515
+ })
516
+ Button('splice')
517
+ .onClick(() => {
518
+ this.arr.numberArr.splice(1, 0, 60);
519
+ })
520
+ Button('unshift')
521
+ .onClick(() => {
522
+ this.arr.numberArr.unshift(100);
523
+ })
524
+ Button('copywithin')
525
+ .onClick(() => {
526
+ this.arr.numberArr.copyWithin(0, 1, 2);
527
+ })
528
+ Button('fill')
529
+ .onClick(() => {
530
+ this.arr.numberArr.fill(0, 2, 4);
531
+ })
532
+ Button('reverse')
533
+ .onClick(() => {
534
+ this.arr.numberArr.reverse();
535
+ })
536
+ Button('sort')
537
+ .onClick(() => {
538
+ this.arr.numberArr.sort();
539
+ })
540
+ }
541
+ }
542
+ }
543
+ ```
544
+ #### @Trace装饰对象数组
545
+ - @Trace装饰对象数组personList以及Person类中的age属性,因此当personList、age改变时均可以观测到变化。
546
+ - 点击Text组件更改age时,Text组件会刷新。
547
+ ```
548
+ let nextId: number = 0;
549
+ @ObservedV2
550
+ class Person {
551
+ // @Trace装饰Person类中的age属性,使age可以被观测
552
+ @Trace public age: number = 0;
553
+ constructor(age: number) {
554
+ this.age = age;
555
+ }
556
+ }
557
+ @ObservedV2
558
+ class Info {
559
+ public id: number = 0;
560
+ @Trace public personList: Person[] = [];
561
+ constructor() {
562
+ this.id = nextId++;
563
+ this.personList = [new Person(0), new Person(1), new Person(2)];
564
+ }
565
+ }
566
+ @Entry
567
+ @ComponentV2
568
+ struct Index {
569
+ info: Info = new Info();
570
+ build() {
571
+ Column() {
572
+ Text(`length: ${this.info.personList.length}`)
573
+ .fontSize(40)
574
+ Divider()
575
+ if (this.info.personList.length >= 3) {
576
+ Text(`${this.info.personList[0].age}`)
577
+ .fontSize(40)
578
+ .onClick(() => {
579
+ this.info.personList[0].age++;
580
+ })
581
+ Text(`${this.info.personList[1].age}`)
582
+ .fontSize(40)
583
+ .onClick(() => {
584
+ this.info.personList[1].age++;
585
+ })
586
+ Text(`${this.info.personList[2].age}`)
587
+ .fontSize(40)
588
+ .onClick(() => {
589
+ this.info.personList[2].age++;
590
+ })
591
+ }
592
+ Divider()
593
+ ForEach(this.info.personList, (item: Person, index: number) => {
594
+ Text(`${index} ${item.age}`)
595
+ .fontSize(40)
596
+ })
597
+ }
598
+ }
599
+ }
600
+ ```
601
+ #### @Trace装饰Map类型
602
+ - 被@Trace装饰的Map类型属性可以观测到调用API带来的变化,包括 set、clear、delete。
603
+ - 因为Info类被@ObservedV2装饰且属性memberMap被@Trace装饰,点击Button('init map')对memberMap赋值也可以观测到变化。
604
+ ```
605
+ @ObservedV2
606
+ class Info {
607
+ @Trace public memberMap: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
608
+ }
609
+ @Entry
610
+ @ComponentV2
611
+ struct MapSample {
612
+ info: Info = new Info();
613
+ build() {
614
+ Row() {
615
+ Column() {
616
+ ForEach(Array.from(this.info.memberMap.entries()), (item: [number, string]) => {
617
+ Text(`${item[0]}`)
618
+ .fontSize(30)
619
+ Text(`${item[1]}`)
620
+ .fontSize(30)
621
+ Divider()
622
+ })
623
+ // 被@Trace装饰的Map类型属性可以观测到调用API带来的变化
624
+ Button('init map')
625
+ .onClick(() => {
626
+ this.info.memberMap = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
627
+ })
628
+ Button('set new one')
629
+ .onClick(() => {
630
+ this.info.memberMap.set(4, 'd');
631
+ })
632
+ Button('clear')
633
+ .onClick(() => {
634
+ this.info.memberMap.clear();
635
+ })
636
+ Button('set the key: 0')
637
+ .onClick(() => {
638
+ this.info.memberMap.set(0, 'aa');
639
+ })
640
+ Button('delete the first one')
641
+ .onClick(() => {
642
+ this.info.memberMap.delete(0);
643
+ })
644
+ }
645
+ .width('100%')
646
+ }
647
+ .height('100%')
648
+ }
649
+ }
650
+ ```
651
+ #### @Trace装饰Set类型
652
+ - 被@Trace装饰的Set类型属性可以观测到调用API带来的变化,包括 add、clear和delete。
653
+ - 因为Info类被@ObservedV2装饰且属性memberSet被@Trace装饰,点击Button('init set')对memberSet赋值也可以观测到变化。
654
+ ```
655
+ @ObservedV2
656
+ class Info {
657
+ @Trace public memberSet: Set<number> = new Set([0, 1, 2, 3, 4]);
658
+ }
659
+ @Entry
660
+ @ComponentV2
661
+ struct SetSample {
662
+ info: Info = new Info();
663
+ build() {
664
+ Row() {
665
+ Column() {
666
+ ForEach(Array.from(this.info.memberSet.entries()), (item: [number, number]) => {
667
+ Text(`${item[0]}`)
668
+ .fontSize(30)
669
+ Divider()
670
+ })
671
+ // 被@Trace装饰的Set类型属性可以观测到调用API带来的变化
672
+ Button('init set')
673
+ .onClick(() => {
674
+ this.info.memberSet = new Set([0, 1, 2, 3, 4]);
675
+ })
676
+ Button('set new one')
677
+ .onClick(() => {
678
+ this.info.memberSet.add(5);
679
+ })
680
+ Button('clear')
681
+ .onClick(() => {
682
+ this.info.memberSet.clear();
683
+ })
684
+ Button('delete the first one')
685
+ .onClick(() => {
686
+ this.info.memberSet.delete(0);
687
+ })
688
+ }
689
+ .width('100%')
690
+ }
691
+ .height('100%')
692
+ }
693
+ }
694
+ ```
695
+ #### @Trace装饰Date类型
696
+ - @Trace装饰的Date类型属性可以观测调用API带来的变化,包括 setFullYear、setMonth、setDate、setHours、setMinutes、setSeconds、setMilliseconds、setTime、setUTCFullYear、setUTCMonth、setUTCDate、setUTCHours、setUTCMinutes、setUTCSeconds、setUTCMilliseconds。
697
+ - 因为Info类被@ObservedV2装饰且属性selectedDate被@Trace装饰,点击Button('set selectedDate to 2023-07-08')对selectedDate赋值也可以观测到变化。
698
+ ```
699
+ @ObservedV2
700
+ class Info {
701
+ @Trace public selectedDate: Date = new Date('2021-08-08');
702
+ }
703
+ @Entry
704
+ @ComponentV2
705
+ struct DateSample {
706
+ info: Info = new Info();
707
+ build() {
708
+ Column() {
709
+ // @Trace装饰的Date类型属性可以观测调用API带来的变化
710
+ Button('set selectedDate to 2023-07-08')
711
+ .margin(10)
712
+ .onClick(() => {
713
+ this.info.selectedDate = new Date('2023-07-08');
714
+ })
715
+ Button('increase the year by 1')
716
+ .margin(10)
717
+ .onClick(() => {
718
+ this.info.selectedDate.setFullYear(this.info.selectedDate.getFullYear() + 1);
719
+ })
720
+ Button('increase the month by 1')
721
+ .margin(10)
722
+ .onClick(() => {
723
+ this.info.selectedDate.setMonth(this.info.selectedDate.getMonth() + 1);
724
+ })
725
+ Button('increase the day by 1')
726
+ .margin(10)
727
+ .onClick(() => {
728
+ this.info.selectedDate.setDate(this.info.selectedDate.getDate() + 1);
729
+ })
730
+ DatePicker({
731
+ start: new Date('1970-1-1'),
732
+ end: new Date('2100-1-1'),
733
+ selected: this.info.selectedDate
734
+ })
735
+ }.width('100%')
736
+ }
737
+ }
738
+ ```
739
+ #### 常见问题
740
+ #### @ObservedV2装饰对象的序列化与反序列化
741
+ @ObservedV2装饰的对象序列化后会为@Trace装饰的属性添加__ob_前缀。
742
+ ```
743
+ @ObservedV2
744
+ class Info {
745
+ @Trace name: string = 'Tom';
746
+ @Trace age: number = 24;
747
+ }
748
+ let realInfo: Info = new Info();
749
+ let jsonResult: string = JSON.stringify(realInfo); // '{"__ob_name":"Tom","__ob_age":24}'
750
+ ```
751
+ 将@ObservedV2装饰的对象通过JSON.stringify序列化后,再通过JSON.parse反序列化,将失去观察能力。
752
+ ```
753
+ @ObservedV2
754
+ class Info {
755
+ @Trace name: string = 'Tom';
756
+ @Trace age: number = 24;
757
+ }
758
+ let realInfo: Info = new Info();
759
+ let jsonResult: string = JSON.stringify(realInfo); // '{"__ob_name":"Tom","__ob_age":24}'
760
+ let parseInfo: Info = JSON.parse(jsonResult);
761
+ // 与直接通过new操作符创建的对象不同,JSON.parse获得的对象实际并不是Info的实例,所以无属性观察能力
762
+ let isInfoByNew: boolean = realInfo instanceof Info; // true
763
+ let isInfoByParse: boolean = parseInfo instanceof Info; // false
764
+ ```
765
+ 可以配合三方库 [class-transformer](https://gitcode.com/openharmony-tpc/openharmony_tpc_samples/tree/master/class-transformer) 实现反序列化后可观察。
766
+ class-transformer可以通过如下命令安装。
767
+ ```
768
+ ohpm install class-transformer
769
+ ```
770
+ ```
771
+ import { plainToInstance } from 'class-transformer'; // 导入三方库
772
+ @ObservedV2
773
+ class Info {
774
+ @Trace name: string = 'Tom';
775
+ @Trace age: number = 24;
776
+ }
777
+ let realInfo: Info = new Info();
778
+ let jsonResult: string = JSON.stringify(realInfo); // '{"__ob_name":"Tom","__ob_age":24}'
779
+ let parseInfo: Info = JSON.parse(jsonResult);
780
+ let transformedInfo: Info = plainToInstance(Info, parseInfo);
781
+ let isInfoByTransformed: boolean = transformedInfo instanceof Info; // true
782
+ ```
783
+ 若为多层对象嵌套场景,需要进行额外处理,包括:
784
+ - 去除序列化结果中的__ob_前缀,否则内层对象无法被正确转换。
785
+ - 使用class-transformer库中提供的@Type装饰器(为与状态管理V2的[@Type装饰器](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-new-type)区分,示例中重命名为TypeFromLibrary)标记里层对象的类型。
786
+ 使用三方库的@Type装饰器需要安装 [reflect-metadata](https://gitcode.com/openharmony-tpc/openharmony_tpc_samples/tree/master/reflect-metadata) 。
787
+ reflect-metadata可以通过如下命令安装。
788
+ ```
789
+ ohpm install reflect-metadata@0.2.1
790
+ ```
791
+ ```
792
+ import { plainToInstance, Type as TypeFromLibrary} from 'class-transformer'; // 导入三方库
793
+ import 'reflect-metadata'; // 三方库的@Type装饰器需要使用
794
+ @ObservedV2
795
+ class Info {
796
+ @Trace name: string = 'Tom';
797
+ @Trace age: number = 24;
798
+ }
799
+ @ObservedV2
800
+ class InfoWrapper {
801
+ // 使用三方库的@Type装饰器(重命名为TypeFromLibrary)标记内层属性的类型
802
+ @TypeFromLibrary(() => Info)
803
+ @Trace info: Info = new Info();
804
+ }
805
+ let realWrapper: InfoWrapper = new InfoWrapper();
806
+ let infoWrapperJson: string = JSON.stringify(realWrapper); // '{"__ob_info":{"__ob_name":"Tom","__ob_age":24}}'
807
+ // 去除属性key的'__ob_'前缀,此处仅做演示,开发者需根据实际类型定义情况完成去除key中的'__ob_'前缀
808
+ let jsonHandled = infoWrapperJson.replaceAll('__ob_', ''); // '{"info":{"name":"Tom","age":24}}'
809
+ let wrapperHandled = plainToInstance(InfoWrapper, JSON.parse(jsonHandled));
810
+ let isWrapper: boolean = wrapperHandled instanceof InfoWrapper; // true
811
+ let isInfo: boolean = (wrapperHandled.info) instanceof Info; // true
812
+ ```
813
+ 在UI中使用的完整示例如下。
814
+ ```
815
+ import { plainToInstance, Type as TypeFromLibrary } from 'class-transformer'; // 导入三方库
816
+ import 'reflect-metadata'; // 三方库的@Type装饰器需要使用
817
+ // 模拟json键值对对象
818
+ let testJSON: Record<string, ESObject> = {
819
+ 'id': 1,
820
+ 'info': {
821
+ 'name': 'Tom',
822
+ 'age': 24
823
+ },
824
+ 'friends': [
825
+ {
826
+ 'name': 'John',
827
+ 'age': 23
828
+ },
829
+ {
830
+ 'name': 'Mary',
831
+ 'age': 24
832
+ }
833
+ ]
834
+ }
835
+ @ObservedV2
836
+ class Info {
837
+ @Trace public name?: string;
838
+ @Trace public age?: number;
839
+ }
840
+ @ObservedV2
841
+ class Person {
842
+ public id?: number;
843
+ // 使用三方库的@Type装饰器(重命名为TypeFromLibrary)标记内层属性的类型
844
+ @TypeFromLibrary(() => Info)
845
+ @Trace public info?: Info;
846
+ // 使用三方库的@Type装饰器(重命名为TypeFromLibrary)标记内层属性的类型
847
+ @TypeFromLibrary(() => Info)
848
+ @Trace public friends?: Info[];
849
+ }
850
+ @Entry
851
+ @ComponentV2
852
+ struct SerializationAndDeserialization {
853
+ @Local person: Person | undefined = undefined;
854
+ aboutToAppear(): void {
855
+ this.person = plainToInstance(Person, testJSON); // 直接将对象通过plainToInstance转为Person实例
856
+ }
857
+ build() {
858
+ Column() {
859
+ Text(`name: ${this.person?.info?.name}, age: ${this.person?.info?.age}`)
860
+ .onClick(() => {
861
+ if (this.person?.info?.age) {
862
+ this.person!.info!.age++; // 修改可观察
863
+ }
864
+ })
865
+ ForEach(this.person?.friends, (item: Info) => {
866
+ Text(`friend name: ${item.name}, age: ${item.age}`)
867
+ .onClick(() => {
868
+ if (item.age) {
869
+ item.age++; // 修改可观察
870
+ }
871
+ })
872
+ })
873
+ Button('Refresh Info')
874
+ .onClick(() => {
875
+ let json: string =
876
+ `{
877
+ "id":12,
878
+ "__ob_info":
879
+ {
880
+ "__ob_name":"Jimmy",
881
+ "__ob_age":35
882
+ },
883
+ "__ob_friends":[
884
+ {
885
+ "__ob_name":"Bob",
886
+ "__ob_age":30
887
+ },
888
+ {
889
+ "__ob_name":"Kevin",
890
+ "__ob_age":33
891
+ }
892
+ ]
893
+ }`;
894
+ // 去除'__ob_'前缀后通过JSON.parse与plainToInstance将json字符串转化成Person对象
895
+ this.person = plainToInstance(Person, JSON.parse(json.replaceAll('__ob_', '')));
896
+ })
897
+ }
898
+ }
899
+ }
900
+ ```
901
+ #### router传递的@ObservedV2类型显示异常
902
+ 用router传递的@ObservedV2类,由于经过序列化生成的属性名称与类中的原始属性名称不一致,不能直接通过as类型转换成@ObservedV2的实例,需要反序列化重新生成@ObservedV2实例。反序列化相关内容请参考 [@ObservedV2装饰对象的序列化与反序列化](#observedv2装饰对象的序列化与反序列化) 。
903
+ 【反例】
904
+ ```
905
+ // 文件pages/faqs/RouterIndex.ets内容
906
+ @ObservedV2
907
+ export class RouterModel {
908
+ @Trace id: number = -1;
909
+ @Trace info: string = 'default';
910
+ }
911
+ @Entry
912
+ @ComponentV2
913
+ struct RouterIndex {
914
+ @Local paramsInfo: RouterModel = new RouterModel();
915
+ onJumpClick(): void {
916
+ this.paramsInfo.id = 0;
917
+ this.paramsInfo.info = 'RouterModel';
918
+ this.getUIContext().getRouter().pushUrl({
919
+ url: 'pages/faqs/ChildPage',
920
+ params: this.paramsInfo // 传递@ObservedV2实例到子页面
921
+ }, (err) => {
922
+ if (err) {
923
+ console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
924
+ return;
925
+ }
926
+ console.info('Invoke pushUrl succeeded.');
927
+ })
928
+ }
929
+ build() {
930
+ Column() {
931
+ Text('Parent page')
932
+ Button('Jump')
933
+ .onClick(() => {
934
+ this.onJumpClick();
935
+ })
936
+ }
937
+ }
938
+ }
939
+ ```
940
+ ```
941
+ // 文件pages/faqs/ChildPage.ets内容
942
+ import { RouterModel } from './RouterIndex';
943
+ @Entry
944
+ @ComponentV2
945
+ struct Detail {
946
+ @Local params?: RouterModel
947
+ aboutToAppear(): void {
948
+ // 错误使用方式!@ObservedV2类型通过router传递无法直接类型转换
949
+ this.params = this.getUIContext().getRouter().getParams() as RouterModel;
950
+ }
951
+ build() {
952
+ Column() {
953
+ Text(`Detail Page: ${this.params?.id} ${this.params?.info}`) // 由于传递数据失败,这里会显示undefined
954
+ }
955
+ }
956
+ }
957
+ ```
958
+ 【正例】
959
+ ```
960
+ @ObservedV2
961
+ export class RouterModel {
962
+ @Trace public id: number = -1;
963
+ @Trace public info: string = 'default';
964
+ }
965
+ @Entry
966
+ @ComponentV2
967
+ struct RouterIndex {
968
+ @Local paramsInfo: RouterModel = new RouterModel();
969
+ onJumpClick(): void {
970
+ this.paramsInfo.id = 0;
971
+ this.paramsInfo.info = 'RouterModel';
972
+ this.getUIContext().getRouter().pushUrl({
973
+ url: 'pages/faqs/ChildPage',
974
+ params: this.paramsInfo // 传递@ObservedV2实例到子页面
975
+ }, (err) => {
976
+ if (err) {
977
+ console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
978
+ return;
979
+ }
980
+ console.info('Invoke pushUrl succeeded.');
981
+ })
982
+ }
983
+ build() {
984
+ Column() {
985
+ Text('Parent page')
986
+ Button('Jump')
987
+ .onClick(() => {
988
+ this.onJumpClick();
989
+ })
990
+ }
991
+ }
992
+ }
993
+ ```
994
+ ```
995
+ import { RouterModel } from './RouterIndex';
996
+ import { plainToInstance } from 'class-transformer'; // 导入三方库
997
+ @Entry
998
+ @ComponentV2
999
+ struct Detail {
1000
+ @Local params?: RouterModel
1001
+ aboutToAppear(): void {
1002
+ this.params =
1003
+ plainToInstance(RouterModel, JSON.parse(JSON.stringify(this.getUIContext().getRouter().getParams())));
1004
+ }
1005
+ build() {
1006
+ Column() {
1007
+ Text(`Detail Page: ${this.params?.id} ${this.params?.info}`)
1008
+ }
1009
+ }
1010
+ }
1011
+ ```
1012
+ ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/85/v3/a5jWMEaMRjm_1MoJWa8M7w/zh-cn_image_0000002659219423.gif?HW-CC-KV=V1&HW-CC-Date=20260701T104555Z&HW-CC-Expire=86400&HW-CC-Sign=F7910C338BB815DE3F5D14C2CE2B1217FA5A4E1A54A124CEAA7E0DAB509D46E1)