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