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