• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Prop装饰器:父子单向同步
2
3
4\@Prop装饰的变量可以和父组件建立单向的同步关系。\@Prop装饰的变量是可变的,但是变化不会同步回其父组件。
5
6
7> **说明:**
8>
9> 从API version 9开始,该装饰器支持在ArkTS卡片中使用。
10
11
12## 概述
13
14\@Prop装饰的变量和父组件建立单向的同步关系:
15
16- \@Prop变量允许在本地修改,但修改后的变化不会同步回父组件。
17
18- 当数据源更改时,\@Prop装饰的变量都会更新,并且会覆盖本地所有更改。因此,数值的同步是父组件到子组件(所属组件),子组件数值的变化不会同步到父组件。
19
20
21
22## 限制条件
23
24- \@Prop装饰变量时会进行深拷贝,在拷贝的过程中除了基本类型、Map、Set、Date、Array外,都会丢失类型。例如[PixelMap](../reference/apis/js-apis-image.md#pixelmap7)等通过NAPI提供的复杂类型,由于有部分实现在Native侧,因此无法在ArkTS侧通过深拷贝获得完整的数据。
25
26- \@Prop装饰器不能在\@Entry装饰的自定义组件中使用。
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/>支持类型的场景请参考[观察变化](#观察变化)。<br/>必须指定类型。<br/>**说明** :<br/>不支持Length、ResourceStr、ResourceColor类型,Length,ResourceStr、ResourceColor为简单类型和复杂类型的联合类型。<br/>在父组件中,传递给\@Prop装饰的值不能为undefined或者null,反例如下所示。<br/>CompA&nbsp;({&nbsp;aProp:&nbsp;undefined&nbsp;})<br/>CompA&nbsp;({&nbsp;aProp:&nbsp;null&nbsp;})<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简单类型的同步)。 |
36| 嵌套传递层数        | 在组件复用场景,建议@Prop深度嵌套数据不要超过5层,嵌套太多会导致深拷贝占用的空间过大以及GarbageCollection(垃圾回收),引起性能问题,此时更建议使用[\@ObjectLink](arkts-observed-and-objectlink.md)。 |
37| 被装饰变量的初始值   | 允许本地初始化。                                 |
38
39
40## 变量的传递/访问规则说明
41
42| 传递/访问     | 说明                                       |
43| --------- | ---------------------------------------- |
44| 从父组件初始化   | 如果本地有初始化,则是可选的。没有的话,则必选,支持父组件中的常规变量(常规变量对@Prop赋值,只是数值的初始化,常规变量的变化不会触发UI刷新。只有状态变量才能触发UI刷新)、\@State、\@Link、\@Prop、\@Provide、\@Consume、\@ObjectLink、\@StorageLink、\@StorageProp、\@LocalStorageLink和\@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```
78class ClassA {
79  public value: string;
80  constructor(value: string) {
81    this.value = value;
82  }
83}
84class Model {
85  public value: string;
86  public a: ClassA;
87  constructor(value: string, a: ClassA) {
88    this.value = value;
89    this.a = a;
90  }
91}
92
93@Prop title: Model;
94// 可以观察到第一层的变化
95this.title.value = 'Hi'
96// 观察不到第二层的变化
97this.title.a.value = 'ArkUi'
98```
99
100对于嵌套场景,如果class是被\@Observed装饰的,可以观察到class属性的变化,示例请参考[@Prop嵌套场景](#prop嵌套场景)。
101
102- 当装饰的类型是数组的时候,可以观察到数组本身的赋值、添加、删除和更新。
103
104```
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### 框架行为
181
182要理解\@Prop变量值初始化和更新机制,有必要了解父组件和拥有\@Prop变量的子组件初始渲染和更新流程。
183
1841. 初始渲染:
185   1. 执行父组件的build()函数将创建子组件的新实例,将数据源传递给子组件;
186   2. 初始化子组件\@Prop装饰的变量。
187
1882. 更新:
189   1. 子组件\@Prop更新时,更新仅停留在当前子组件,不会同步回父组件;
190   2. 当父组件的数据源更新时,子组件的\@Prop装饰的变量将被来自父组件的数据源重置,所有\@Prop装饰的本地的修改将被父组件的更新覆盖。
191
192> **说明:**
193>
194> \@Prop装饰的数据更新依赖其所属自定义组件的重新渲染,所以在应用进入后台后,\@Prop无法刷新,推荐使用\@Link代替。
195
196
197## 使用场景
198
199
200### 父组件\@State到子组件\@Prop简单数据类型同步
201
202
203以下示例是\@State到子组件\@Prop简单数据同步,父组件ParentComponent的状态变量countDownStartValue初始化子组件CountDownComponent中\@Prop装饰的count,点击“Try again”,count的修改仅保留在CountDownComponent 不会同步给父组件ParentComponent。
204
205
206ParentComponent的状态变量countDownStartValue的变化将重置CountDownComponent的count。
207
208
209
210```ts
211@Component
212struct CountDownComponent {
213  @Prop count: number = 0;
214  costOfOneAttempt: number = 1;
215
216  build() {
217    Column() {
218      if (this.count > 0) {
219        Text(`You have ${this.count} Nuggets left`)
220      } else {
221        Text('Game over!')
222      }
223      // @Prop装饰的变量不会同步给父组件
224      Button(`Try again`).onClick(() => {
225        this.count -= this.costOfOneAttempt;
226      })
227    }
228  }
229}
230
231@Entry
232@Component
233struct ParentComponent {
234  @State countDownStartValue: number = 10;
235
236  build() {
237    Column() {
238      Text(`Grant ${this.countDownStartValue} nuggets to play.`)
239      // 父组件的数据源的修改会同步给子组件
240      Button(`+1 - Nuggets in New Game`).onClick(() => {
241        this.countDownStartValue += 1;
242      })
243      // 父组件的修改会同步给子组件
244      Button(`-1  - Nuggets in New Game`).onClick(() => {
245        this.countDownStartValue -= 1;
246      })
247
248      CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 })
249    }
250  }
251}
252```
253
254
255在上面的示例中:
256
257
2581. CountDownComponent子组件首次创建时其\@Prop装饰的count变量将从父组件\@State装饰的countDownStartValue变量初始化;
259
2602. 按“+1”或“-1”按钮时,父组件的\@State装饰的countDownStartValue值会变化,这将触发父组件重新渲染,在父组件重新渲染过程中会刷新使用countDownStartValue状态变量的UI组件并单向同步更新CountDownComponent子组件中的count值;
261
2623. 更新count状态变量值也会触发CountDownComponent的重新渲染,在重新渲染过程中,评估使用count状态变量的if语句条件(this.count &gt; 0),并执行true分支中的使用count状态变量的UI组件相关描述来更新Text组件的UI显示;
263
2644. 当按下子组件CountDownComponent的“Try again”按钮时,其\@Prop变量count将被更改,但是count值的更改不会影响父组件的countDownStartValue值;
265
2665. 父组件的countDownStartValue值会变化时,父组件的修改将覆盖掉子组件CountDownComponent中count本地的修改。
267
268
269### 父组件\@State数组项到子组件\@Prop简单数据类型同步
270
271
272父组件中\@State如果装饰的数组,其数组项也可以初始化\@Prop。以下示例中父组件Index中\@State装饰的数组arr,将其数组项初始化子组件Child中\@Prop装饰的value。
273
274
275
276```ts
277@Component
278struct Child {
279  @Prop value: number = 0;
280
281  build() {
282    Text(`${this.value}`)
283      .fontSize(50)
284      .onClick(() => {
285        this.value++
286      })
287  }
288}
289
290@Entry
291@Component
292struct Index {
293  @State arr: number[] = [1, 2, 3];
294
295  build() {
296    Row() {
297      Column() {
298        Child({ value: this.arr[0] })
299        Child({ value: this.arr[1] })
300        Child({ value: this.arr[2] })
301
302        Divider().height(5)
303
304        ForEach(this.arr,
305          (item: number) => {
306            Child({ value: item })
307          },
308          (item: string) => item.toString()
309        )
310        Text('replace entire arr')
311          .fontSize(50)
312          .onClick(() => {
313            // 两个数组都包含项“3”。
314            this.arr = this.arr[0] == 1 ? [3, 4, 5] : [1, 2, 3];
315          })
316      }
317    }
318  }
319}
320```
321
322
323初始渲染创建6个子组件实例,每个\@Prop装饰的变量初始化都在本地拷贝了一份数组项。子组件onclick事件处理程序会更改局部变量值。
324
325
326如果点击界面上的“1”六下、“2”五下、“3”四下,将所有变量的本地取值都变为“7”。
327
328
329
330```
3317
3327
3337
334----
3357
3367
3377
338```
339
340
341单击replace entire arr后,屏幕将显示以下信息。
342
343
344
345```
3463
3474
3485
349----
3507
3514
3525
353```
354
355
356- 在子组件Child中做的所有的修改都不会同步回父组件Index组件,所以即使6个组件显示都为7,但在父组件Index中,this.arr保存的值依旧是[1,2,3]。
357
358- 点击replace entire arr,this.arr[0] == 1成立,将this.arr赋值为[3, 4, 5];
359
360- 因为this.arr[0]已更改,Child({value: this.arr[0]})组件将this.arr[0]更新同步到实例\@Prop装饰的变量。Child({value: this.arr[1]})和Child({value: this.arr[2]})的情况也类似。
361
362
363- 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”。
364
365
366### 从父组件中的\@State类对象属性到\@Prop简单类型的同步
367
368如果图书馆有一本图书和两位用户,每位用户都可以将图书标记为已读,此标记行为不会影响其它读者用户。从代码角度讲,对\@Prop图书对象的本地更改不会同步给图书馆组件中的\@State图书对象。
369
370在此示例中,图书类可以使用\@Observed装饰器,但不是必须的,只有在嵌套结构时需要此装饰器。这一点我们会在[从父组件中的@State数组项到@Prop class类型的同步](#从父组件中的state数组项到prop-class类型的同步)说明。
371
372
373```ts
374class Book {
375  public title: string;
376  public pages: number;
377  public readIt: boolean = false;
378
379  constructor(title: string, pages: number) {
380    this.title = title;
381    this.pages = pages;
382  }
383}
384
385@Component
386struct ReaderComp {
387  @Prop book: Book = new Book("", 0);
388
389  build() {
390    Row() {
391      Text(this.book.title)
392      Text(`...has${this.book.pages} pages!`)
393      Text(`...${this.book.readIt ? "I have read" : 'I have not read it'}`)
394        .onClick(() => this.book.readIt = true)
395    }
396  }
397}
398
399@Entry
400@Component
401struct Library {
402  @State book: Book = new Book('100 secrets of C++', 765);
403
404  build() {
405    Column() {
406      ReaderComp({ book: this.book })
407      ReaderComp({ book: this.book })
408    }
409  }
410}
411```
412
413### 从父组件中的\@State数组项到\@Prop class类型的同步
414
415在下面的示例中,更改了\@State 装饰的allBooks数组中Book对象上的属性,但点击“Mark read for everyone”无反应。这是因为该属性是第二层的嵌套属性,\@State装饰器只能观察到第一层属性,不会观察到此属性更改,所以框架不会更新ReaderComp。
416
417```ts
418let nextId: number = 1;
419
420// @Observed
421class Book {
422  public id: number;
423  public title: string;
424  public pages: number;
425  public readIt: boolean = false;
426
427  constructor(title: string, pages: number) {
428    this.id = nextId++;
429    this.title = title;
430    this.pages = pages;
431  }
432}
433
434@Component
435struct ReaderComp {
436  @Prop book: Book = new Book("", 1);
437
438  build() {
439    Row() {
440      Text(` ${this.book ? this.book.title : "Book is undefined"}`).fontColor('#e6000000')
441      Text(` has ${this.book ? this.book.pages : "Book is undefined"} pages!`).fontColor('#e6000000')
442      Text(` ${this.book ? this.book.readIt ? "I have read" : 'I have not read it' : "Book is undefined"}`).fontColor('#e6000000')
443        .onClick(() => this.book.readIt = true)
444    }
445  }
446}
447
448@Entry
449@Component
450struct Library {
451  @State allBooks: Book[] = [new Book("C#", 765), new Book("JS", 652), new Book("TS", 765)];
452
453  build() {
454    Column() {
455      Text('library`s all time favorite')
456        .width(312)
457        .height(40)
458        .backgroundColor('#0d000000')
459        .borderRadius(20)
460        .margin(12)
461        .padding({ left: 20 })
462        .fontColor('#e6000000')
463      ReaderComp({ book: this.allBooks[2] })
464        .backgroundColor('#0d000000')
465        .width(312)
466        .height(40)
467        .padding({ left: 20, top: 10 })
468        .borderRadius(20)
469        .colorBlend('#e6000000')
470      Divider()
471      Text('Books on loaan to a reader')
472        .width(312)
473        .height(40)
474        .backgroundColor('#0d000000')
475        .borderRadius(20)
476        .margin(12)
477        .padding({ left: 20 })
478        .fontColor('#e6000000')
479      ForEach(this.allBooks, (book: Book) => {
480        ReaderComp({ book: book })
481          .margin(12)
482          .width(312)
483          .height(40)
484          .padding({ left: 20, top: 10 })
485          .backgroundColor('#0d000000')
486          .borderRadius(20)
487      },
488        (book: Book) => book.id.toString())
489      Button('Add new')
490        .width(312)
491        .height(40)
492        .margin(12)
493        .fontColor('#FFFFFF 90%')
494        .onClick(() => {
495          this.allBooks.push(new Book("JA", 512));
496        })
497      Button('Remove first book')
498        .width(312)
499        .height(40)
500        .margin(12)
501        .fontColor('#FFFFFF 90%')
502        .onClick(() => {
503          if (this.allBooks.length > 0){
504            this.allBooks.shift();
505          } else {
506            console.log("length <= 0")
507          }
508        })
509      Button("Mark read for everyone")
510        .width(312)
511        .height(40)
512        .margin(12)
513        .fontColor('#FFFFFF 90%')
514        .onClick(() => {
515          this.allBooks.forEach((book) => book.readIt = true)
516        })
517    }
518  }
519}
520```
521
522 需要使用\@Observed装饰class Book,Book的属性将被观察。 需要注意的是,\@Prop在子组件装饰的状态变量和父组件的数据源是单向同步关系,即ReaderComp中的\@Prop book的修改不会同步给父组件Library。而父组件只会在数值有更新的时候(和上一次状态的对比),才会触发UI的重新渲染。
523
524```ts
525@Observed
526class Book {
527  public id: number;
528  public title: string;
529  public pages: number;
530  public readIt: boolean = false;
531
532  constructor(title: string, pages: number) {
533    this.id = nextId++;
534    this.title = title;
535    this.pages = pages;
536  }
537}
538```
539
540\@Observed装饰的类的实例会被不透明的代理对象包装,此代理可以检测到包装对象内的所有属性更改。如果发生这种情况,此时,代理通知\@Prop,\@Prop对象值被更新。
541
542![Video-prop-UsageScenario-one](figures/Video-prop-UsageScenario-one.gif)
543
544### \@Prop本地初始化不和父组件同步
545
546为了支持\@Component装饰的组件复用场景,\@Prop支持本地初始化,这样可以让\@Prop是否与父组件建立同步关系变得可选。当且仅当\@Prop有本地初始化时,从父组件向子组件传递\@Prop的数据源才是可选的。
547
548下面的示例中,子组件包含两个\@Prop变量:
549
550- \@Prop customCounter没有本地初始化,所以需要父组件提供数据源去初始化\@Prop,并当父组件的数据源变化时,\@Prop也将被更新;
551
552- \@Prop customCounter2有本地初始化,在这种情况下,\@Prop依旧允许但非强制父组件同步数据源给\@Prop。
553
554
555```ts
556@Component
557struct MyComponent {
558  @Prop customCounter: number;
559  @Prop customCounter2: number = 5;
560
561  build() {
562    Column() {
563      Row() {
564        Text(`From Main: ${this.customCounter}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 })
565      }
566
567      Row() {
568        Button('Click to change locally !')
569          .width(288)
570          .height(40)
571          .margin({ left: 30, top: 12 })
572          .fontColor('#FFFFFF,90%')
573          .onClick(() => {
574            this.customCounter2++
575          })
576      }
577
578      Row() {
579        Text(`Custom Local: ${this.customCounter2}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 })
580      }
581    }
582  }
583}
584
585@Entry
586@Component
587struct MainProgram {
588  @State mainCounter: number = 10;
589
590  build() {
591    Column() {
592      Row() {
593        Column() {
594          // customCounter必须从父组件初始化,因为MyComponent的customCounter成员变量缺少本地初始化;此处,customCounter2可以不做初始化。
595          MyComponent({ customCounter: this.mainCounter })
596          // customCounter2也可以从父组件初始化,父组件初始化的值会覆盖子组件customCounter2的本地初始化的值
597          MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter })
598        }
599      }
600
601      Row() {
602        Column() {
603          Button('Click to change number')
604            .width(288)
605            .height(40)
606            .margin({ left: 30, top: 12 })
607            .fontColor('#FFFFFF,90%')
608            .onClick(() => {
609              this.mainCounter++
610            })
611        }
612      }
613    }
614  }
615}
616```
617
618![Video-prop-UsageScenario-two](figures/Video-prop-UsageScenario-two.gif)
619
620### \@Prop嵌套场景
621
622在嵌套场景下,每一层都要用@Observed装饰,且每一层都要被@Prop接收,这样才能观察到嵌套场景。
623
624```ts
625// 以下是嵌套类对象的数据结构。
626@Observed
627class ClassA {
628  public title: string;
629
630  constructor(title: string) {
631    this.title = title;
632  }
633}
634
635@Observed
636class ClassB {
637  public name: string;
638  public a: ClassA;
639
640  constructor(name: string, a: ClassA) {
641    this.name = name;
642    this.a = a;
643  }
644}
645```
646
647以下组件层次结构呈现的是@Prop嵌套场景的数据结构。
648
649```ts
650@Entry
651@Component
652struct Parent {
653  @State votes: ClassB = new ClassB('Hello', new ClassA('world'))
654
655  build() {
656    Column() {
657      Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
658        Button('change ClassB name')
659          .width(312)
660          .height(40)
661          .margin(12)
662          .fontColor('#FFFFFF,90%')
663          .onClick(() => {
664            this.votes.name = "aaaaa"
665          })
666        Button('change ClassA title')
667          .width(312)
668          .height(40)
669          .margin(12)
670          .fontColor('#FFFFFF,90%')
671          .onClick(() => {
672            this.votes.a.title = "wwwww"
673          })
674        Text(this.votes.name)
675          .fontSize(16)
676          .margin(12)
677          .width(312)
678          .height(40)
679          .backgroundColor('#ededed')
680          .borderRadius(20)
681          .textAlign(TextAlign.Center)
682          .fontColor('#e6000000')
683          .onClick(() => {
684            this.votes.name = 'Bye'
685          })
686        Text(this.votes.a.title)
687          .fontSize(16)
688          .margin(12)
689          .width(312)
690          .height(40)
691          .backgroundColor('#ededed')
692          .borderRadius(20)
693          .textAlign(TextAlign.Center)
694          .onClick(() => {
695            this.votes.a.title = "openHarmony"
696          })
697        Child1({ vote1: this.votes.a })
698      }
699
700    }
701
702  }
703}
704
705
706@Component
707struct Child1 {
708  @Prop vote1: ClassA = new ClassA('');
709
710  build() {
711    Column() {
712      Text(this.vote1.title)
713        .fontSize(16)
714        .margin(12)
715        .width(312)
716        .height(40)
717        .backgroundColor('#ededed')
718        .borderRadius(20)
719        .textAlign(TextAlign.Center)
720        .onClick(() => {
721          this.vote1.title = 'Bye Bye'
722        })
723    }
724  }
725}
726```
727
728![Video-prop-UsageScenario-three](figures/Video-prop-UsageScenario-three.gif)
729
730## 常见问题
731
732### \@Prop装饰状态变量未初始化错误
733
734\@Prop需要被初始化,如果没有进行本地初始化的,则必须通过父组件进行初始化。如果进行了本地初始化,那么是可以不通过父组件进行初始化的。
735
736【反例】
737
738```ts
739@Observed
740class ClassA {
741  public c: number = 0;
742
743  constructor(c: number) {
744    this.c = c;
745  }
746}
747
748@Component
749struct PropChild {
750  @Prop testNum: ClassA; // 未进行本地初始化
751
752  build() {
753    Text(`PropChild testNum ${this.testNum.c}`)
754      .onClick(() => {
755        this.testNum.c += 1;
756      })
757  }
758}
759
760@Entry
761@Component
762struct Parent {
763  @State testNum: ClassA[] = [new ClassA(1)];
764
765  build() {
766    Column() {
767      Text(`Parent testNum ${this.testNum[0].c}`)
768        .onClick(() => {
769          this.testNum[0].c += 1;
770        })
771
772      // @Prop本地没有初始化,也没有从父组件初始化
773      PropChild1()
774    }
775  }
776}
777```
778
779【正例】
780
781```ts
782@Observed
783class ClassA {
784  public c: number = 0;
785
786  constructor(c: number) {
787    this.c = c;
788  }
789}
790
791@Component
792struct PropChild1 {
793  @Prop testNum: ClassA; // 未进行本地初始化
794
795  build() {
796    Text(`PropChild1 testNum ${this.testNum.c}`)
797      .onClick(() => {
798        this.testNum.c += 1;
799      })
800  }
801}
802
803@Component
804struct PropChild2 {
805  @Prop testNum: ClassA = new ClassA(1); // 进行本地初始化
806
807  build() {
808    Text(`PropChild2 testNum ${this.testNum.c}`)
809      .onClick(() => {
810        this.testNum.c += 1;
811      })
812  }
813}
814
815@Entry
816@Component
817struct Parent {
818  @State testNum: ClassA[] = [new ClassA(1)];
819
820  build() {
821    Column() {
822      Text(`Parent testNum ${this.testNum[0].c}`)
823        .onClick(() => {
824          this.testNum[0].c += 1;
825        })
826
827      // @PropChild1本地没有初始化,必须从父组件初始化
828      PropChild1({ testNum: this.testNum[0] })
829      // @PropChild2本地进行了初始化,可以不从父组件初始化,也可以从父组件初始化
830      PropChild2()
831      PropChild2({ testNum: this.testNum[0] })
832    }
833  }
834}
835```
836
837<!--no_check-->
838