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