• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Prop装饰器:父子单向同步
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @jiyujia926-->
5<!--Designer: @s10021109-->
6<!--Tester: @TerryTsao-->
7<!--Adviser: @zhang_yixin13-->
8
9\@Prop装饰的变量可以和父组件建立单向同步关系。
10
11在阅读\@Prop文档前,建议开发者首先了解[\@State](./arkts-state.md)的基本用法。最佳实践请参考[状态管理最佳实践](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-status-management)12
13> **说明:**
14>
15> 从API version 9开始,该装饰器支持在ArkTS卡片中使用。
16>
17> 从API version 11开始,该装饰器支持在原子化服务中使用。
18
19## 概述
20
21\@Prop装饰的变量具有以下特性:
22
23- \@Prop装饰的变量允许本地修改,但修改不会同步回父组件。
24
25- 当数据源更改时,\@Prop装饰的变量都会更新,并且会覆盖本地所有更改。
26
27## 装饰器使用规则说明
28
29| \@Prop变量装饰器 | 说明                                       |
30| ----------- | ---------------------------------------- |
31| 装饰器参数       | 无。                                        |
32| 同步类型        | 单向同步。对父组件状态变量值的修改,将同步给子组件\@Prop装饰的变量,子组件\@Prop装饰的变量的修改不会同步到父组件的状态变量上。<br>嵌套类型的场景请参考[观察变化](#观察变化)。 |
33| 允许装饰的变量类型   |  Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>API version 10开始支持[Date类型](#装饰date类型变量)。<br/>API version 11及以上支持[Map](#装饰map类型变量)、[Set](#装饰set类型变量)类型、undefined和null类型、ArkUI框架定义的联合类型[Length](../../reference/apis-arkui/arkui-ts/ts-types.md#length)、[ResourceStr](../../reference/apis-arkui/arkui-ts/ts-types.md#resourcestr)、[ResourceColor](../../reference/apis-arkui/arkui-ts/ts-types.md#resourcecolor)类型以及这些类型的联合类型,示例见[Prop支持联合类型实例](#prop支持联合类型实例)。<br/>支持类型的场景请参考[观察变化](#观察变化)。|
34| 不允许装饰的变量类型 | 不支持装饰Function类型。      |
35| 嵌套传递层数        | 在组件复用场景,建议@Prop深度嵌套数据不要超过5层,嵌套太多会导致深拷贝占用的空间过大以及GarbageCollection(垃圾回收),引起性能问题,此时更建议使用[\@ObjectLink](arkts-observed-and-objectlink.md)。 |
36| 被装饰变量的初始值   | 允许本地初始化。API version 11及以上,如果和[\@Require](arkts-require.md)结合使用,则必须父组件构造传参。 |
37
38
39## 变量的传递/访问规则说明
40
41| 装饰器使用规则          | 说明                                                         |
42| ------------------ | ------------------------------------------------------------ |
43| 从父组件初始化     | 如果本地有初始化,则是可选的,初始化行为和[\@State](./arkts-state.md#变量的传递访问规则说明)保持一致。没有的话,则必选,支持父组件中的常规变量(常规变量对@Prop赋值,只是数值的初始化,常规变量的变化不会触发UI刷新。只有状态变量才能触发UI刷新)、[\@State](arkts-state.md)、[\@Link](arkts-link.md)、\@Prop、[\@Provide](arkts-provide-and-consume.md)、[\@Consume](arkts-provide-and-consume.md)、[\@ObjectLink](arkts-observed-and-objectlink.md)、[\@StorageLink](arkts-appstorage.md#storagelink)、[\@StorageProp](arkts-appstorage.md#storageprop)、[\@LocalStorageLink](arkts-localstorage.md#localstoragelink)和[\@LocalStorageProp](arkts-localstorage.md#localstorageprop)去初始化子组件中的\@Prop变量。 |
44|用于初始化子组件| \@Prop支持初始化子组件中的常规变量、\@State、\@Link、\@Prop、\@Provide。|
45| 是否支持组件外访问 | \@Prop装饰的变量是私有的,只能在组件内访问。                 |
46
47 初始化规则图示:
48
49![zh-cn_image_0000001552972029](figures/zh-cn_image_0000001552972029.png)
50
51## 观察变化和行为表现
52
53### 观察变化
54
55\@Prop装饰的数据可以观察到以下变化。
56
57- 当装饰支持类型,可以观察到赋值的变化。
58
59  ```ts
60  // 简单类型
61  @Prop count: number;
62  // 赋值的变化可以被观察到
63  this.count = 1;
64  // 复杂类型
65  @Prop title: Model;
66  // 可以观察到赋值的变化
67  this.title = new Model('Hi');
68  ```
69
70- 当装饰的类型是Object或者class复杂类型时,可以观察到自身的赋值和第一层的属性的变化,属性即object.keys(observedObject)返回的所有属性。
71
72```ts
73class Info {
74  public value: string;
75  constructor(value: string) {
76    this.value = value;
77  }
78}
79class Model {
80  public value: string;
81  public info: Info;
82  constructor(value: string, info: Info) {
83    this.value = value;
84    this.info = info;
85  }
86}
87
88@Prop title: Model;
89// 可以观察到第一层的变化
90this.title.value = 'Hi';
91// 观察不到第二层的变化
92this.title.info.value = 'ArkUI';
93```
94
95对于嵌套场景,如果class是被\@Observed装饰的,可以观察到class属性的变化,示例请参考[@Prop嵌套场景](#prop嵌套场景)。
96
97- 当装饰的类型是数组的时候,可以观察到数组本身的赋值和数组项的添加、删除和更新。
98
99```ts
100// @State装饰的对象为数组时
101@Prop title: string[];
102// 数组自身的赋值可以观察到
103this.title = ['1'];
104// 数组项的赋值可以观察到
105this.title[0] = '2';
106// 删除数组项可以观察到
107this.title.pop();
108// 新增数组项可以观察到
109this.title.push('3');
110```
111
112对于\@State和\@Prop的同步场景:
113
114- 使用父组件中\@State变量的值初始化子组件中的\@Prop变量。当\@State变量变化时,该变量值也会同步更新至\@Prop变量。
115- \@Prop装饰的变量的修改不会影响其数据源\@State装饰变量的值。
116- 除了\@State,数据源也可以用\@Link或\@Prop装饰,对\@Prop的同步机制是相同的。
117- 数据源和\@Prop变量的类型需要相同,\@Prop允许简单类型和class类型。
118
119- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性,详见[装饰Date类型变量](#装饰date类型变量)。
120
121- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。
122
123- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。
124
125### 框架行为
126
127理解\@Prop变量值初始化和更新机制,需要了解父组件和子组件的渲染和更新流程。
128
1291. 初始渲染:
130   1. 执行父组件的build()函数,创建子组件的新实例并传递数据源。
131   2. 初始化子组件\@Prop装饰的变量。
132
1332. 更新:
134   1. 子组件\@Prop更新时,更新仅停留在当前子组件,不会同步回父组件。
135   2. 当父组件的数据源更新时,子组件的\@Prop装饰的变量将被来自父组件的数据源重置,所有@Prop装饰变量的本地修改将被父组件的更新覆盖。
136
137> **说明:**
138>
139> \@Prop同步数据源依赖于数据源所在组件的刷新,而应用进入后台后无法触发刷新,因此应用进入后台后,@Prop无法从数据源更新。在此场景下,若需即时数据同步,推荐使用@Link代替。
140
141以下示例中,当@State装饰的变量message改变时,Father组件会刷新。由于Son组件使用@Prop接收了该变量,因此Father组件刷新的过程中会使用message的最新值去更新@Prop的值。@Prop更新后,会触发Son组件的刷新。
142
143```ts
144@Component
145struct Son {
146  @Prop message: string = 'Hi';
147
148  build() {
149    Column() {
150      Text(this.message)
151    }
152  }
153}
154
155@Entry
156@Component
157struct Father {
158  @State message: string = 'Hello';
159
160  build() {
161    Column() {
162      Text(this.message)
163      Button(`father click`).onClick(() => {
164        this.message += '*';
165      })
166      Son({ message: this.message })
167    }
168  }
169}
170```
171
172## 限制条件
173
174- \@Prop装饰变量时会进行深拷贝,在拷贝的过程中除了基本类型、Map、Set、Date、Array外,都会丢失类型。例如[PixelMap](../../reference/apis-image-kit/arkts-apis-image-PixelMap.md)等通过NAPI提供的复杂类型,由于有部分实现在Native侧,因此无法在ArkTS侧通过深拷贝获得完整的数据。
175
176## 使用场景
177
178### 父组件\@State到子组件\@Prop简单数据类型同步
179
180以下示例是\@State到子组件\@Prop简单数据同步,父组件ParentComponent的状态变量countDownStartValue初始化子组件CountDownComponent中\@Prop装饰的count,点击“Try again”,count的修改仅保留在CountDownComponent,不会同步给父组件ParentComponent。
181
182ParentComponent的状态变量countDownStartValue的变化将重置CountDownComponent的count。
183
184```ts
185@Component
186struct CountDownComponent {
187  @Prop count: number = 0;
188  costOfOneAttempt: number = 1;
189
190  build() {
191    Column() {
192      if (this.count > 0) {
193        Text(`You have ${this.count} Nuggets left`)
194      } else {
195        Text('Game over!')
196      }
197      // @Prop装饰的变量不会同步给父组件
198      Button(`Try again`).onClick(() => {
199        this.count -= this.costOfOneAttempt;
200      })
201    }
202  }
203}
204
205@Entry
206@Component
207struct ParentComponent {
208  @State countDownStartValue: number = 10;
209
210  build() {
211    Column() {
212      Text(`Grant ${this.countDownStartValue} nuggets to play.`)
213      // 父组件的数据源的修改会同步给子组件
214      Button(`+1 - Nuggets in New Game`).onClick(() => {
215        this.countDownStartValue += 1;
216      })
217      // 父组件的修改会同步给子组件
218      Button(`-1  - Nuggets in New Game`).onClick(() => {
219        this.countDownStartValue -= 1;
220      })
221
222      CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 })
223    }
224  }
225}
226```
227
228在上面的示例中:
229
2301. CountDownComponent子组件首次创建时其\@Prop装饰的count变量将从父组件\@State装饰的countDownStartValue变量初始化。
231
2322. 按“+1”或“-1”按钮时,父组件的\@State装饰的countDownStartValue值会变化,这将触发父组件重新渲染,在父组件重新渲染过程中会刷新使用countDownStartValue状态变量的UI组件,并单向同步更新CountDownComponent子组件中的count值。
233
2343. 更新count状态变量值也会触发CountDownComponent的重新渲染,在重新渲染过程中,评估使用count状态变量的if语句条件(this.count &gt; 0),并执行true分支中的使用count状态变量的UI组件相关描述来更新Text组件的UI显示。
235
2364. 当按下子组件CountDownComponent的“Try again”按钮时,其\@Prop变量count将被更改,但是count值的更改不会影响父组件的countDownStartValue值。
237
2385. 父组件的countDownStartValue值变化时,父组件的修改将覆盖掉子组件CountDownComponent中count本地的修改。
239
240### 父组件\@State数组项到子组件\@Prop简单数据类型同步
241
242父组件中@State如果装饰数组类型的变量,其数组项也可以初始化@Prop。以下示例中,父组件Index中@State装饰数组arr,将其数组项初始化子组件Child中@Prop装饰的value。
243
244```ts
245@Component
246struct Child {
247  @Prop value: number = 0;
248
249  build() {
250    Text(`${this.value}`)
251      .fontSize(50)
252      .onClick(() => {
253        this.value++;
254      })
255  }
256}
257
258@Entry
259@Component
260struct Index {
261  @State arr: number[] = [1, 2, 3];
262
263  build() {
264    Row() {
265      Column() {
266        Child({ value: this.arr[0] })
267        Child({ value: this.arr[1] })
268        Child({ value: this.arr[2] })
269
270        Divider().height(5)
271
272        ForEach(this.arr,
273          (item: number) => {
274            Child({ value: item })
275          },
276          (item: number) => item.toString()
277        )
278        Text('replace entire arr')
279          .fontSize(50)
280          .onClick(() => {
281            // 两个数组都包含项“3”。
282            this.arr = this.arr[0] == 1 ? [3, 4, 5] : [1, 2, 3];
283          })
284      }
285    }
286  }
287}
288```
289
290初始渲染创建6个子组件实例,每个\@Prop装饰的变量初始化都在本地拷贝了一份数组项。子组件onClick事件处理程序会更改局部变量值。
291
292如果点击界面上的“1”六次,“2”五次、“3”四次,将所有变量的本地取值都变为“7”。
293
294```
2957
2967
2977
298——————
2997
3007
3017
302```
303
304点击replace entire arr后,屏幕将显示以下信息。
305
306```
3073
3084
3095
310——————
3117
3124
3135
314```
315
316- 在子组件Child中做的所有的修改都不会同步回父组件Index组件,所以即使6个组件显示都为7,但在父组件Index中,this.arr保存的值依旧是[1,2,3]。
317
318- 点击replace entire arr,this.arr[0] == 1成立,将this.arr赋值为[3, 4, 5]。
319
320- 因为this.arr[0]已更改,Child({value: this.arr[0]})组件将this.arr[0]更新同步到实例\@Prop装饰的变量。Child({value: this.arr[1]})和Child({value: this.arr[2]})的情况也类似。
321
322- this.arr的更改触发ForEach更新,this.arr更新的前后都有数值为3的数组项:[3, 4, 5] 和[1, 2, 3]。根据diff算法,数组项“3”将被保留,删除“1”和“2”的数组项,添加为“4”和“5”的数组项。这就意味着,数组项“3”的组件不会重新生成,而是将其移动到第一位。所以“3”对应的组件不会更新,此时“3”对应的组件数值为“7”,ForEach最终的渲染结果是“7”,“4”,“5”。
323
324### 从父组件中的\@State类对象属性到\@Prop简单类型的同步
325
326如果图书馆有一本图书和两位用户,每位用户都可以将图书标记为已读,此标记行为不会影响其他用户。从代码角度讲,对\@Prop图书对象的本地更改不会同步给图书馆组件中的\@State图书对象。
327
328在此示例中,图书类可以使用\@Observed装饰器,但不是必须的,只有在嵌套结构时需要此装饰器。这一点会在[从父组件中的\@State数组项到\@Prop class类型的同步](#从父组件中的state数组项到prop-class类型的同步)说明。
329
330```ts
331class Book {
332  public title: string;
333  public pages: number;
334  public readIt: boolean = false;
335
336  constructor(title: string, pages: number) {
337    this.title = title;
338    this.pages = pages;
339  }
340}
341
342@Component
343struct ReaderComp {
344  @Prop book: Book = new Book('', 0);
345
346  build() {
347    Row() {
348      Text(this.book.title)
349      Text(`...has${this.book.pages} pages!`)
350      Text(`...${this.book.readIt ? 'I have read' : 'I have not read it'}`)
351        .onClick(() => this.book.readIt = true)
352    }
353  }
354}
355
356@Entry
357@Component
358struct Library {
359  @State book: Book = new Book('100 secrets of C++', 765);
360
361  build() {
362    Column() {
363      ReaderComp({ book: this.book })
364      ReaderComp({ book: this.book })
365    }
366  }
367}
368```
369
370### 从父组件中的\@State数组项到\@Prop class类型的同步
371
372以下示例中,更改了\@State装饰的allBooks数组中Book对象的属性,但点击“Mark read for everyone”时,没有触发UI更新。这是因为该属性是第二层的嵌套属性,\@State装饰器只能观察到第一层属性,不会观察到此属性更改,所以框架不会更新ReaderComp。
373
374```ts
375let nextId: number = 1;
376
377// @Observed
378class Book {
379  public id: number;
380  public title: string;
381  public pages: number;
382  public readIt: boolean = false;
383
384  constructor(title: string, pages: number) {
385    this.id = nextId++;
386    this.title = title;
387    this.pages = pages;
388  }
389}
390
391@Component
392struct ReaderComp {
393  @Prop book: Book = new Book('', 1);
394
395  build() {
396    Row() {
397      Text(` ${this.book ? this.book.title : 'Book is undefined'}`).fontColor('#e6000000')
398      Text(` has ${this.book ? this.book.pages : 'Book is undefined'} pages!`).fontColor('#e6000000')
399      Text(` ${this.book ? this.book.readIt ? 'I have read' : 'I have not read it' : 'Book is undefined'}`)
400        .fontColor('#e6000000')
401        .onClick(() => this.book.readIt = true)
402    }
403  }
404}
405
406@Entry
407@Component
408struct Library {
409  @State allBooks: Book[] = [new Book('C#', 765), new Book('JS', 652), new Book('TS', 765)];
410
411  build() {
412    Column() {
413      Text('library`s all time favorite')
414        .width(312)
415        .height(40)
416        .backgroundColor('#0d000000')
417        .borderRadius(20)
418        .margin(12)
419        .padding({ left: 20 })
420        .fontColor('#e6000000')
421      ReaderComp({ book: this.allBooks[2] })
422        .backgroundColor('#0d000000')
423        .width(312)
424        .height(40)
425        .padding({ left: 20, top: 10 })
426        .borderRadius(20)
427        .colorBlend('#e6000000')
428      Text('Books on loan to a reader')
429        .width(312)
430        .height(40)
431        .backgroundColor('#0d000000')
432        .borderRadius(20)
433        .margin(12)
434        .padding({ left: 20 })
435        .fontColor('#e6000000')
436      ForEach(this.allBooks, (book: Book) => {
437        ReaderComp({ book: book })
438          .margin(12)
439          .width(312)
440          .height(40)
441          .padding({ left: 20, top: 10 })
442          .backgroundColor('#0d000000')
443          .borderRadius(20)
444      },
445        (book: Book) => book.id.toString())
446      Button('Add new')
447        .width(312)
448        .height(40)
449        .margin(12)
450        .fontColor('#FFFFFF')
451        .onClick(() => {
452          this.allBooks.push(new Book('JA', 512));
453        })
454      Button('Remove first book')
455        .width(312)
456        .height(40)
457        .margin(12)
458        .fontColor('#FFFFFF')
459        .onClick(() => {
460          if (this.allBooks.length > 0) {
461            this.allBooks.shift();
462          } else {
463            console.info('length <= 0');
464          }
465        })
466      Button('Mark read for everyone')
467        .width(312)
468        .height(40)
469        .margin(12)
470        .fontColor('#FFFFFF')
471        .onClick(() => {
472          this.allBooks.forEach((book) => book.readIt = true)
473        })
474    }
475  }
476}
477```
478
479使用\@Observed装饰class Book,Book的属性变化将被观察。需要注意的是,\@Prop在子组件装饰的状态变量和父组件的数据源是单向同步关系,即ReaderComp中的\@Prop book的修改不会同步给父组件Library。而父组件只会在状态变量发生变化的时候,才会触发UI的重新渲染。
480
481```ts
482@Observed
483class Book {
484  public id: number;
485  public title: string;
486  public pages: number;
487  public readIt: boolean = false;
488
489  constructor(title: string, pages: number) {
490    this.id = nextId++;
491    this.title = title;
492    this.pages = pages;
493  }
494}
495```
496
497\@Observed装饰的类的实例会被不透明的代理对象包装,此代理可以检测到包装对象内的所有属性更改。如果发生这种情况,此时,代理通知\@Prop,\@Prop对象值被更新。
498
499![Video-prop-UsageScenario-one](figures/Video-prop-UsageScenario-one.gif)
500
501### \@Prop本地初始化不和父组件同步
502
503为了支持\@Component装饰的组件复用场景,\@Prop支持本地初始化,这样可以让\@Prop是否与父组件建立同步关系变得可选。当且仅当\@Prop有本地初始化时,从父组件向子组件传递\@Prop的数据源才是可选的。
504
505下面的示例中,子组件包含两个\@Prop变量:
506
507- \@Prop customCounter没有本地初始化,所以需要父组件提供数据源去初始化\@Prop,并当父组件的数据源变化时,\@Prop也将被更新。
508
509- \@Prop customCounter2有本地初始化,在这种情况下,\@Prop依旧允许但非强制父组件同步数据源给\@Prop。
510
511```ts
512@Component
513struct MyComponent {
514  @Prop customCounter: number;
515  @Prop customCounter2: number = 5;
516
517  build() {
518    Column() {
519      Row() {
520        Text(`From Main: ${this.customCounter}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 })
521      }
522
523      Row() {
524        Button('Click to change locally!')
525          .width(288)
526          .height(40)
527          .margin({ left: 30, top: 12 })
528          .fontColor('#FFFFFF')
529          .onClick(() => {
530            this.customCounter2++;
531          })
532      }
533
534      Row() {
535        Text(`Custom Local: ${this.customCounter2}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 })
536      }
537    }
538  }
539}
540
541@Entry
542@Component
543struct MainProgram {
544  @State mainCounter: number = 10;
545
546  build() {
547    Column() {
548      Row() {
549        Column() {
550          // customCounter必须从父组件初始化,因为MyComponent的customCounter成员变量缺少本地初始化;此处,customCounter2可以不做初始化。
551          MyComponent({ customCounter: this.mainCounter })
552          // customCounter2也可以从父组件初始化,父组件初始化的值会覆盖子组件customCounter2的本地初始化的值
553          MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter })
554        }
555      }
556
557      Row() {
558        Column() {
559          Button('Click to change number')
560            .width(288)
561            .height(40)
562            .margin({ left: 30, top: 12 })
563            .fontColor('#FFFFFF')
564            .onClick(() => {
565              this.mainCounter++;
566            })
567        }
568      }
569    }
570  }
571}
572```
573
574![Video-prop-UsageScenario-two](figures/Video-prop-UsageScenario-two.gif)
575
576### \@Prop嵌套场景
577
578在嵌套场景下,每一层都要用\@Observed装饰,且每一层都要被\@Prop接收,这样才能观察到嵌套场景。
579
580```ts
581// 以下是嵌套类对象的数据结构。
582@Observed
583class Son {
584  public title: string;
585
586  constructor(title: string) {
587    this.title = title;
588  }
589}
590
591@Observed
592class Father {
593  public name: string;
594  public son: Son;
595
596  constructor(name: string, son: Son) {
597    this.name = name;
598    this.son = son;
599  }
600}
601```
602
603以下组件层次结构展示了\@Prop嵌套场景的数据结构。
604
605```ts
606@Entry
607@Component
608struct Person {
609  @State person: Father = new Father('Hello', new Son('world'));
610
611  build() {
612    Column() {
613      Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
614        Button('change Father name')
615          .width(312)
616          .height(40)
617          .margin(12)
618          .fontColor('#FFFFFF')
619          .onClick(() => {
620            this.person.name = 'Hi';
621          })
622        Button('change Son title')
623          .width(312)
624          .height(40)
625          .margin(12)
626          .fontColor('#FFFFFF')
627          .onClick(() => {
628            this.person.son.title = 'ArkUI';
629          })
630        Text(this.person.name)
631          .fontSize(16)
632          .margin(12)
633          .width(312)
634          .height(40)
635          .backgroundColor('#ededed')
636          .borderRadius(20)
637          .textAlign(TextAlign.Center)
638          .fontColor('#e6000000')
639          .onClick(() => {
640            this.person.name = 'Bye';
641          })
642        Text(this.person.son.title)
643          .fontSize(16)
644          .margin(12)
645          .width(312)
646          .height(40)
647          .backgroundColor('#ededed')
648          .borderRadius(20)
649          .textAlign(TextAlign.Center)
650          .onClick(() => {
651            this.person.son.title = 'openHarmony';
652          })
653        Child({ child: this.person.son })
654      }
655    }
656  }
657}
658
659
660@Component
661struct Child {
662  @Prop child: Son = new Son('');
663
664  build() {
665    Column() {
666      Text(this.child.title)
667        .fontSize(16)
668        .margin(12)
669        .width(312)
670        .height(40)
671        .backgroundColor('#ededed')
672        .borderRadius(20)
673        .textAlign(TextAlign.Center)
674        .onClick(() => {
675          this.child.title = 'Bye Bye';
676        })
677    }
678  }
679}
680```
681
682![Video-prop-UsageScenario-three](figures/Video-prop-UsageScenario-three.gif)
683
684### 装饰Map类型变量
685
686> **说明:**
687>
688> 从API version 11开始,\@Prop支持Map类型。
689
690在下面的示例中,value类型为Map\<number, string\>,点击Button改变message的值,视图会随之刷新。
691
692```ts
693@Component
694struct Child {
695  @Prop value: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
696
697  build() {
698    Column() {
699      ForEach(Array.from(this.value.entries()), (item: [number, string]) => {
700        Text(`${item[0]}`).fontSize(30)
701        Text(`${item[1]}`).fontSize(30)
702        Divider()
703      })
704      Button('child init map').onClick(() => {
705        this.value = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
706      })
707      Button('child set new one').onClick(() => {
708        this.value.set(4, 'd');
709      })
710      Button('child clear').onClick(() => {
711        this.value.clear();
712      })
713      Button('child replace the first one').onClick(() => {
714        this.value.set(0, 'aa');
715      })
716      Button('child delete the first one').onClick(() => {
717        this.value.delete(0);
718      })
719    }
720  }
721}
722
723
724@Entry
725@Component
726struct MapSample {
727  @State message: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
728
729  build() {
730    Row() {
731      Column() {
732        Child({ value: this.message })
733      }
734      .width('100%')
735    }
736    .height('100%')
737  }
738}
739```
740
741### 装饰Set类型变量
742
743> **说明:**
744>
745> 从API version 11开始,\@Prop支持Set类型。
746
747在下面的示例中,message类型为Set\<number\>,点击Button改变message的值,视图会随之刷新。
748
749```ts
750@Component
751struct Child {
752  @Prop message: Set<number> = new Set([0, 1, 2, 3, 4]);
753
754  build() {
755    Column() {
756      ForEach(Array.from(this.message.entries()), (item: [number, number]) => {
757        Text(`${item[0]}`).fontSize(30)
758        Divider()
759      })
760      Button('init set').onClick(() => {
761        this.message = new Set([0, 1, 2, 3, 4]);
762      })
763      Button('set new one').onClick(() => {
764        this.message.add(5);
765      })
766      Button('clear').onClick(() => {
767        this.message.clear();
768      })
769      Button('delete the first one').onClick(() => {
770        this.message.delete(0);
771      })
772    }
773    .width('100%')
774  }
775}
776
777
778@Entry
779@Component
780struct SetSample {
781  @State message: Set<number> = new Set([0, 1, 2, 3, 4]);
782
783  build() {
784    Row() {
785      Column() {
786        Child({ message: this.message })
787      }
788      .width('100%')
789    }
790    .height('100%')
791  }
792}
793```
794
795### 装饰Date类型变量
796
797在下面的示例中,selectedDate类型为Date,点击Button改变Date的值,视图会随之刷新。
798
799```ts
800@Component
801struct DateComponent {
802  @Prop selectedDate: Date = new Date('');
803
804  build() {
805    Column() {
806      Button('child update the new date')
807        .margin(10)
808        .onClick(() => {
809          this.selectedDate = new Date('2023-09-09');
810        })
811      Button(`child increase the year by 1`).onClick(() => {
812        this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1);
813      })
814      DatePicker({
815        start: new Date('1970-1-1'),
816        end: new Date('2100-1-1'),
817        selected: this.selectedDate
818      })
819    }
820  }
821}
822
823@Entry
824@Component
825struct ParentComponent {
826  @State parentSelectedDate: Date = new Date('2021-08-08');
827
828  build() {
829    Column() {
830      Button('parent update the new date')
831        .margin(10)
832        .onClick(() => {
833          this.parentSelectedDate = new Date('2023-07-07');
834        })
835      Button('parent increase the day by 1')
836        .margin(10)
837        .onClick(() => {
838          this.parentSelectedDate.setDate(this.parentSelectedDate.getDate() + 1);
839        })
840      DatePicker({
841        start: new Date('1970-1-1'),
842        end: new Date('2100-1-1'),
843        selected: this.parentSelectedDate
844      })
845
846      DateComponent({ selectedDate: this.parentSelectedDate })
847    }
848
849  }
850}
851```
852
853### Prop支持联合类型实例
854
855@Prop支持联合类型和undefined和null,在下面的示例中,animal类型为Animals | undefined,点击父组件Zoo中的Button改变animal的属性或者类型,Child中也会对应刷新。
856
857```ts
858class Animals {
859  public name: string;
860
861  constructor(name: string) {
862    this.name = name;
863  }
864}
865
866@Component
867struct Child {
868  @Prop animal: Animals | undefined;
869
870  build() {
871    Column() {
872      Text(`Child's animal is  ${this.animal instanceof Animals ? this.animal.name : 'undefined'}`).fontSize(30)
873
874      Button('Child change animals into tigers')
875        .onClick(() => {
876          // 赋值为Animals的实例
877          this.animal = new Animals('Tiger');
878        })
879
880      Button('Child change animal to undefined')
881        .onClick(() => {
882          // 赋值为undefined
883          this.animal = undefined;
884        })
885
886    }.width('100%')
887  }
888}
889
890@Entry
891@Component
892struct Zoo {
893  @State animal: Animals | undefined = new Animals('lion');
894
895  build() {
896    Column() {
897      Text(`Parents' animals are  ${this.animal instanceof Animals ? this.animal.name : 'undefined'}`).fontSize(30)
898
899      Child({animal: this.animal})
900
901      Button('Parents change animals into dogs')
902        .onClick(() => {
903          // 判断animal的类型,做属性的更新
904          if (this.animal instanceof Animals) {
905            this.animal.name = 'Dog';
906          } else {
907            console.info('num is undefined, cannot change property');
908          }
909        })
910
911      Button('Parents change animal to undefined')
912        .onClick(() => {
913          // 赋值为undefined
914          this.animal = undefined;
915        })
916    }
917  }
918}
919```
920
921## 常见问题
922
923### \@Prop装饰状态变量未初始化错误
924
925\@Prop需要被初始化,如果没有进行本地初始化的,则必须通过父组件进行初始化。如果进行了本地初始化,那么是可以不通过父组件进行初始化的。
926
927【反例】
928
929```ts
930@Observed
931class Commodity {
932  public price: number = 0;
933
934  constructor(price: number) {
935    this.price = price;
936  }
937}
938
939@Component
940struct PropChild {
941  @Prop fruit: Commodity; // 未进行本地初始化
942
943  build() {
944    Text(`PropChild fruit ${this.fruit.price}`)
945      .onClick(() => {
946        this.fruit.price += 1;
947      })
948  }
949}
950
951@Entry
952@Component
953struct Parent {
954  @State fruit: Commodity[] = [new Commodity(1)];
955
956  build() {
957    Column() {
958      Text(`Parent fruit ${this.fruit[0].price}`)
959        .onClick(() => {
960          this.fruit[0].price += 1;
961        })
962
963      // @Prop本地没有初始化,也没有从父组件初始化
964      PropChild()
965    }
966  }
967}
968```
969
970【正例】
971
972```ts
973@Observed
974class Commodity {
975  public price: number = 0;
976
977  constructor(price: number) {
978    this.price = price;
979  }
980}
981
982@Component
983struct PropChild1 {
984  @Prop fruit: Commodity; // 未进行本地初始化
985
986  build() {
987    Text(`PropChild1 fruit ${this.fruit.price}`)
988      .onClick(() => {
989        this.fruit.price += 1;
990      })
991  }
992}
993
994@Component
995struct PropChild2 {
996  @Prop fruit: Commodity = new Commodity(1); // 进行本地初始化
997
998  build() {
999    Text(`PropChild2 fruit ${this.fruit.price}`)
1000      .onClick(() => {
1001        this.fruit.price += 1;
1002      })
1003  }
1004}
1005
1006@Entry
1007@Component
1008struct Parent {
1009  @State fruit: Commodity[] = [new Commodity(1)];
1010
1011  build() {
1012    Column() {
1013      Text(`Parent fruit ${this.fruit[0].price}`)
1014        .onClick(() => {
1015          this.fruit[0].price += 1;
1016        })
1017
1018      // @PropChild1本地没有初始化,必须从父组件初始化
1019      PropChild1({ fruit: this.fruit[0] })
1020      // @PropChild2本地进行了初始化,可以不从父组件初始化,也可以从父组件初始化
1021      PropChild2()
1022      PropChild2({ fruit: this.fruit[0] })
1023    }
1024  }
1025}
1026```
1027
1028### 使用a.b(this.object)形式调用,不会触发UI刷新
1029
1030在build方法内,当@Prop装饰的变量是Object类型、且通过a.b(this.object)形式调用时,b方法内传入的是this.object的原始对象,修改其属性,无法触发UI刷新。如下例中,通过静态方法Score.changeScore1或者this.changeScore2修改自定义组件Child中的this.score.value时,UI不会刷新。
1031
1032【反例】
1033
1034```ts
1035class Score {
1036  value: number;
1037  constructor(value: number) {
1038    this.value = value;
1039  }
1040
1041  static changeScore1(param1:Score) {
1042    param1.value += 1;
1043  }
1044}
1045
1046@Entry
1047@Component
1048struct Parent {
1049  @State score: Score = new Score(1);
1050
1051  build() {
1052    Column({space:8}) {
1053      Text(`The value in Parent is ${this.score.value}.`)
1054        .fontSize(30)
1055        .fontColor(Color.Red)
1056      Child({ score: this.score })
1057    }
1058    .width('100%')
1059    .height('100%')
1060  }
1061}
1062
1063@Component
1064struct Child {
1065  @Prop score: Score;
1066
1067  changeScore2(param2:Score) {
1068    param2.value += 2;
1069  }
1070
1071  build() {
1072    Column({space:8}) {
1073      Text(`The value in Child is ${this.score.value}.`)
1074        .fontSize(30)
1075      Button(`changeScore1`)
1076        .onClick(()=>{
1077          // 通过静态方法调用,无法触发UI刷新
1078          Score.changeScore1(this.score);
1079        })
1080      Button(`changeScore2`)
1081        .onClick(()=>{
1082          // 使用this通过自定义组件内部方法调用,无法触发UI刷新
1083          this.changeScore2(this.score);
1084        })
1085    }
1086  }
1087}
1088```
1089
1090可以通过如下先赋值、再调用新赋值的变量的方式为this.score加上Proxy代理,实现UI刷新。
1091
1092【正例】
1093
1094```ts
1095class Score {
1096  value: number;
1097  constructor(value: number) {
1098    this.value = value;
1099  }
1100
1101  static changeScore1(score:Score) {
1102    score.value += 1;
1103  }
1104}
1105
1106@Entry
1107@Component
1108struct Parent {
1109  @State score: Score = new Score(1);
1110
1111  build() {
1112    Column({space:8}) {
1113      Text(`The value in Parent is ${this.score.value}.`)
1114        .fontSize(30)
1115        .fontColor(Color.Red)
1116      Child({ score: this.score })
1117    }
1118    .width('100%')
1119    .height('100%')
1120  }
1121}
1122
1123@Component
1124struct Child {
1125  @Prop score: Score;
1126
1127  changeScore2(score:Score) {
1128    score.value += 2;
1129  }
1130
1131  build() {
1132    Column({space:8}) {
1133      Text(`The value in Child is ${this.score.value}.`)
1134        .fontSize(30)
1135      Button(`changeScore1`)
1136        .onClick(()=>{
1137          // 通过赋值添加 Proxy 代理
1138          let score1 = this.score;
1139          Score.changeScore1(score1);
1140        })
1141      Button(`changeScore2`)
1142        .onClick(()=>{
1143          // 通过赋值添加 Proxy 代理
1144          let score2 = this.score;
1145          this.changeScore2(score2);
1146        })
1147    }
1148  }
1149}
1150```
1151<!--no_check-->
1152