• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Prop Decorator: One-Way Synchronization from the Parent Component to Child Components
2
3
4One-way synchronization is supported between an \@Prop decorated variable a variable of its parent component. This means that, an \@Prop decorated variable is mutable, and its changes will not be synchronized to the parent component.
5
6Before reading this topic, you are advised to understand the basic usage of [\@State](./arkts-state.md).
7
8> **NOTE**
9>
10> This decorator can be used in ArkTS widgets since API version 9.
11>
12> This decorator can be used in atomic services since API version 11.
13
14## Overview
15
16For the \@Prop decorated variable of a child component, the change synchronization to the parent component is uni-directional.
17
18- An \@Prop variable is allowed to be modified locally, but the change does not propagate back to its parent component.
19
20- Whenever the data source changes, the \@Prop decorated variable gets updated, and any locally made changes are overwritten. In other words, the change is synchronized from the parent component to the (owning) child component, but not the other way around.
21
22## Constraints
23
24- When decorating variables, \@Prop makes a deep copy, during which all types, except primitive types, Map, Set, Date, and Array, will be lost. For example, for complex types provided by N-API, such as [PixelMap](../reference/apis-image-kit/js-apis-image.md#pixelmap7), because they are partially implemented in the native code, complete data cannot be obtained through a deep copy in ArkTS.
25
26
27## Rules of Use
28
29| \@Prop Decorator| Description                                      |
30| ----------- | ---------------------------------------- |
31| Decorator parameters      | None.                                       |
32| Synchronization type       | One-way: from the data source provided by the parent component to the \@Prop decorated variable. For details about the scenarios of nested types, see [Observed Changes](#observed-changes).|
33| Allowed variable types  | Object, class, string, number, Boolean, enum, and array of these types.<br>**undefined** or **null** (**any** is not supported).<br>Date type.<br>(Applicable to API version 11 or later) Map and Set types.<br>The union types defined by the ArkUI framework, including Length, ResourceStr, and ResourceColor, are supported.<br>The type must be specified.<br>The type must be the same as that of the [data source](arkts-state-management-overview.md#basic-concepts). There are three cases:<br>- Synchronizing the \@Prop decorated variable from a variable decorated by \@State or other decorators. Example: [Simple Type @Prop Synced from @State in Parent Component](#simple-type-prop-synced-from-state-in-parent-component).<br>- Synchronizing the \@Prop decorated variable from the item of an array decorated by an \@State or other decorators. Example: [Simple Type @Prop Synced from @State Array Item in Parent Component](#simple-type-prop-synced-from-state-array-item-in-parent-component).<br>- Synchronizing the \@Prop decorated variable from a state property of the Object or class type in the parent component. Example: [Class Object Type @Prop Synced from @State Class Object Property in Parent Component](#class-object-type-prop-synced-from-state-class-object-property-in-parent-component).<br>For details about the scenarios of supported types, see [Observed Changes](#observed-changes).<br>(Applicable to API version 11 or later) Union type of the preceding types, for example, **string \| number**, **string \| undefined**, or **ClassA \| null**. For details, see [Union Type @Prop](#union-type-prop).<br>**NOTE**<br>When **undefined** or **null** is used, you are advised to explicitly specify the type to pass the TypeScript type check. For example, **@Prop a: string \| undefined = undefined** is supported, but **@Prop a: string = undefined** is not.|
34| Number of nested layers       | In component reuse scenarios, it is recommended that @Prop be nested with no more than five layers of data. If @Prop is nested with too many layers of data, garbage collection and increased memory usage caused by deep copy will arise, resulting in performance issues. To avoid such issues, use [\@ObjectLink](arkts-observed-and-objectlink.md) instead.|
35| Initial value for the decorated variable  | Local initialization is allowed. If this decorator is used together with [\@Require](arkts-require.md) in API version 11, the parent component must construct input parameters.|
36
37
38## Variable Transfer/Access Rules
39
40| Transfer/Access         | Description                                                        |
41| ------------------ | ------------------------------------------------------------ |
42| Initialization from the parent component    | If initialization is performed locally, this operation is optional. The initialization behavior is the same as that in [\@State](./arkts-state.md#). If local initialization cannot be performed, this operation is mandatory. An @Prop decorated variable can be initialized from a regular variable (whose change does not trigger UI re-render). or an [\@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), or [\@LocalStorageProp](arkts-localstorage.md#localstorageprop) decorated variable in its parent component.|
43| Child component initialization  | \@Prop can be used for initialization of a regular variable or \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
44| Access| Private, accessible only within the component.                |
45
46**Figure 1** Initialization rule
47
48
49![en-us_image_0000001552972029](figures/en-us_image_0000001552972029.png)
50
51
52## Observed Changes and Behavior
53
54
55### Observed Changes
56
57\@Prop decorated variables can observe the following changes:
58
59- When the decorated variable is of the Object, class, string, number, Boolean, or enum type, its value change can be observed.
60
61  ```ts
62  // Simple type
63  @Prop count: number;
64  // The value change can be observed.
65  this.count = 1;
66  // Complex type
67  @Prop title: Model;
68  // The value change can be observed.
69  this.title = new Model('Hi');
70  ```
71
72- When the decorated variable is of the Object or class type, the value changes of properties at the first layer, that is, the properties that **Object.keys(observedObject)** returns, can be observed.
73
74```ts
75class Info {
76  public value: string;
77  constructor(value: string) {
78    this.value = value;
79  }
80}
81class Model {
82  public value: string;
83  public info: Info;
84  constructor(value: string, info: Info) {
85    this.value = value;
86    this.info = info;
87  }
88}
89
90@Prop title: Model;
91// The value changes at the first layer can be observed.
92this.title.value = 'Hi';
93// The value changes at the second layer cannot be observed.
94this.title.info.value = 'ArkUI';
95```
96
97In the scenarios of nested objects, if a class is decorated by \@Observed, the value changes of the class property can be observed. For details, see [@Prop Nesting Scenario](#prop-nesting-scenario).
98
99- When the decorated variable is of the array type, the value change of the array as well as the addition, deletion, and update of array items can be observed.
100
101```ts
102// Assume that the object decorated by @State is an array.
103@Prop title: string[];
104// The value change of the array itself can be observed.
105this.title = ['1'];
106// The value change of array items can be observed.
107this.title[0] = '2';
108// The deletion of array items can be observed.
109this.title.pop();
110// The addition of array items can be observed.
111this.title.push('3');
112```
113
114For synchronization between \@State and \@Prop decorated variables:
115
116- The value of an \@State decorated variable in the parent component is used to initialize an \@Prop decorated variable in the child component. Any change to an \@State decorated variable is updated to the @Prop decorated variable.
117- However, any change to the @Prop decorated variable does not affect the value of its source @State decorated variable.
118- In addition to \@State, the source can also be decorated with \@Link or \@Prop, where the mechanisms for syncing the \@Prop decorated variable is the same.
119- The source and \@Prop decorated variable must be of the same type. The \@Prop decorated variable can be of simple and class types.
120
121- When the decorated variable is of the Date type, the value change of the **Date** object can be observed, and the following APIs can be called to update **Date** properties: **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, and **setUTCMilliseconds**.
122
123```ts
124@Component
125struct DateComponent {
126  @Prop selectedDate: Date = new Date('');
127
128  build() {
129    Column() {
130      Button('child update the new date')
131        .margin(10)
132        .onClick(() => {
133          this.selectedDate = new Date('2023-09-09');
134        })
135      Button(`child increase the year by 1`).onClick(() => {
136        this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1);
137      })
138      DatePicker({
139        start: new Date('1970-1-1'),
140        end: new Date('2100-1-1'),
141        selected: this.selectedDate
142      })
143    }
144  }
145}
146
147@Entry
148@Component
149struct ParentComponent {
150  @State parentSelectedDate: Date = new Date('2021-08-08');
151
152  build() {
153    Column() {
154      Button('parent update the new date')
155        .margin(10)
156        .onClick(() => {
157          this.parentSelectedDate = new Date('2023-07-07');
158        })
159      Button('parent increase the day by 1')
160        .margin(10)
161        .onClick(() => {
162          this.parentSelectedDate.setDate(this.parentSelectedDate.getDate() + 1);
163        })
164      DatePicker({
165        start: new Date('1970-1-1'),
166        end: new Date('2100-1-1'),
167        selected: this.parentSelectedDate
168      })
169
170      DateComponent({ selectedDate: this.parentSelectedDate })
171    }
172
173  }
174}
175```
176
177- When the decorated variable is **Map**, value changes of **Map** can be observed. In addition, you can call the **set**, **clear**, and **delete** APIs of **Map** to update its value. For details, see [Decorating Variables of the Map Type](#decorating-variables-of-the-map-type).
178
179- When the decorated variable is **Set**, value changes of **Set** can be observed. In addition, you can call the **add**, **clear**, and **delete** APIs of **Set** to update its value. For details, see [Decorating Variables of the Set Type](#decorating-variables-of-the-set-type).
180
181### Framework Behavior
182
183To understand the value initialization and update mechanism of the \@Prop decorated variable, it is necessary to understand the parent component and the initial render and update process of the child component that owns the \@Prop decorated variable.
184
1851. Initial render:
186   1. The execution of the parent component's **build()** function creates an instance of the child component, and the parent component provides a source for the \@Prop decorated variable.
187   2. The @Prop decorated variable is initialized.
188
1892. Update:
190   1. When the \@Prop decorated variable is modified locally, the change does not propagate back to its parent component.
191   2. When the data source of the parent component is updated, the \@Prop decorated variable in the child component is reset, and its local value changes are overwritten.
192
193> **NOTE**
194>
195> The update of an \@Prop decorated variable relies on the re-rendering of the owning custom component. As such, when the application is in the background, the \@Prop decorated variable cannot be updated. In this case, use \@Link instead.
196
197
198## Usage Scenarios
199
200
201### Simple Type Sync from @State of the Parent Component to @Prop of the Child Component
202
203
204In this example, the \@Prop decorated **count** variable in the **CountDownComponent** child component is initialized from the \@State decorated **countDownStartValue** variable in the **ParentComponent**. When **Try again** is touched, the value of the **count** variable is modified, but the change remains within the **CountDownComponent** and does not affect the **ParentComponent**.
205
206
207Updating **countDownStartValue** in the **ParentComponent** will update the value of the @Prop decorated **count**.
208
209```ts
210@Component
211struct CountDownComponent {
212  @Prop count: number = 0;
213  costOfOneAttempt: number = 1;
214
215  build() {
216    Column() {
217      if (this.count > 0) {
218        Text(`You have ${this.count} Nuggets left`)
219      } else {
220        Text('Game over!')
221      }
222      // Changes to the @Prop decorated variables are not synchronized to the parent component.
223      Button(`Try again`).onClick(() => {
224        this.count -= this.costOfOneAttempt;
225      })
226    }
227  }
228}
229
230@Entry
231@Component
232struct ParentComponent {
233  @State countDownStartValue: number = 10;
234
235  build() {
236    Column() {
237      Text(`Grant ${this.countDownStartValue} nuggets to play.`)
238      // Changes to the data source provided by the parent component are synchronized to the child component.
239      Button(`+1 - Nuggets in New Game`).onClick(() => {
240        this.countDownStartValue += 1;
241      })
242      // Updating the parent component will also update the child component.
243      Button(`-1  - Nuggets in New Game`).onClick(() => {
244        this.countDownStartValue -= 1;
245      })
246
247      CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 })
248    }
249  }
250}
251```
252
253
254In the preceding example:
255
256
2571. On initial render, when the **CountDownComponent** child component is created, its \@Prop decorated **count** variable is initialized from the \@State decorated **countDownStartValue** variable in the **ParentComponent**.
258
2592. When the "+1" or "-1" button is touched, the \@State decorated **countDownStartValue** of the **ParentComponent** changes. This will cause the **ParentComponent** to re-render. At the minimum, the **CountDownComponent** will be updated because of the change in the **count** variable value.
260
2613. Because of the change in the **count** variable value, the **CountDownComponent** child component will re-render. At a minimum, the **if** statement's condition (**this.counter > 0**) is evaluated, and the **Text** child component inside the **if** statement would be updated.
262
2634. When **Try again** in the **CountDownComponent** child component is touched, the value of the **count** variable is decorated, but the change remains within the child component and does not affect the **countDownStartValue** in the parent component.
264
2655. Updating **countDownStartValue** will overwrite the local value changes of the @Prop decorated **count** in the **CountDownComponent** child component.
266
267
268### Simple Type @Prop Synced from @State Array Item in Parent Component
269
270
271The \@State decorated array an array item in the parent component can be used as data source to initialize and update a @Prop decorated variable. In the following example, the \@State decorated array **arr** in the parent component **Index** initializes the \@Prop decorated **value** variable in the child component **Child**.
272
273```ts
274@Component
275struct Child {
276  @Prop value: number = 0;
277
278  build() {
279    Text(`${this.value}`)
280      .fontSize(50)
281      .onClick(() => {
282        this.value++;
283      })
284  }
285}
286
287@Entry
288@Component
289struct Index {
290  @State arr: number[] = [1, 2, 3];
291
292  build() {
293    Row() {
294      Column() {
295        Child({ value: this.arr[0] })
296        Child({ value: this.arr[1] })
297        Child({ value: this.arr[2] })
298
299        Divider().height(5)
300
301        ForEach(this.arr,
302          (item: number) => {
303            Child({ value: item })
304          },
305          (item: number) => item.toString()
306        )
307        Text('replace entire arr')
308          .fontSize(50)
309          .onClick(() => {
310            // Both arrays contain item "3".
311            this.arr = this.arr[0] == 1 ? [3, 4, 5] : [1, 2, 3];
312          })
313      }
314    }
315  }
316}
317```
318
319
320Initial render creates six instances of the **Child** component. Each \@Prop decorated variable is initialized with a copy of an array item. The **onclick** event handler of the **Child** component changes the local variable value.
321
322
323Click **1** six times, 2 five times, and **3** four times on the page. The local values of all variables are then changed to **7**.
324
325```
3267
3277
3287
329----
3307
3317
3327
333```
334
335
336After **replace entire arr** is clicked, the following information is displayed:
337
338```
3393
3404
3415
342----
3437
3444
3455
346```
347
348
349- Changes made in the **Child** component are not synchronized to the parent component **Index**. Therefore, even if the values of the six instances of the **Child** component are **7**, the value of **this.arr** in the **Index** component is still **[1,2,3]**.
350
351- After **replace entire arr** is clicked, if **this.arr[0] == 1** is true, **this.arr** is set to **[3, 4, 5]**.
352
353- Because **this.arr[0]** has been changed, the **Child({value: this.arr[0]})** component synchronizes the update of **this.arr[0]** to the instance's \@Prop decorated variable. The same happens for **Child({value: this.arr[1]})** and **Child({value: this.arr[2]})**.
354
355
356- The change of **this.arr** causes **ForEach** to update: According to the diff algorithm, the array item with the ID **3** is retained in this update, array items with IDs **1** and **2** are deleted, and array items with IDs **4** and **5** are added. The array before and after the update is **[1, 2, 3]** and **[3, 4, 5]**, respectively. This implies that the **Child** instance generated for item **3** is moved to the first place, but not updated. In this case, the component value corresponding to **3** is **7**, and the final render result of **ForEach** is **7**, **4**, and **5**.
357
358
359### Class Object Type @Prop Synced from @State Class Object Property in Parent Component
360
361In a library with one book and two readers, each reader can mark the book as read, and the marking does not affect the other reader. Technically speaking, local changes to the \@Prop decorated **book** object do not sync back to the @State decorated **book** in the **Library** component.
362
363In this example, the \@Observed decorator can be applied to the **book** class, but it is not mandatory. It is only needed for nested structures. This will be further explained in [Class Type @Prop Synced from @State Array Item in Parent Component](#class-type-prop-synced-from-state-array-item-in-parent-component).
364
365
366```ts
367class Book {
368  public title: string;
369  public pages: number;
370  public readIt: boolean = false;
371
372  constructor(title: string, pages: number) {
373    this.title = title;
374    this.pages = pages;
375  }
376}
377
378@Component
379struct ReaderComp {
380  @Prop book: Book = new Book("", 0);
381
382  build() {
383    Row() {
384      Text(this.book.title)
385      Text(`...has${this.book.pages} pages!`)
386      Text(`...${this.book.readIt ? "I have read" : 'I have not read it'}`)
387        .onClick(() => this.book.readIt = true)
388    }
389  }
390}
391
392@Entry
393@Component
394struct Library {
395  @State book: Book = new Book('100 secrets of C++', 765);
396
397  build() {
398    Column() {
399      ReaderComp({ book: this.book })
400      ReaderComp({ book: this.book })
401    }
402  }
403}
404```
405
406### Class Type @Prop Synced from @State Array Item in Parent Component
407
408In the following example, a property of the **Book** object in the \@State decorated **allBooks** array is changed, but the system does not respond when **Mark read for everyone** is clicked. This is because the property is nested at the second layer, and the \@State decorator can observe only properties at the first layer. Therefore, the framework does not update **ReaderComp**.
409
410```ts
411let nextId: number = 1;
412
413// @Observed
414class Book {
415  public id: number;
416  public title: string;
417  public pages: number;
418  public readIt: boolean = false;
419
420  constructor(title: string, pages: number) {
421    this.id = nextId++;
422    this.title = title;
423    this.pages = pages;
424  }
425}
426
427@Component
428struct ReaderComp {
429  @Prop book: Book = new Book("", 1);
430
431  build() {
432    Row() {
433      Text(` ${this.book ? this.book.title : "Book is undefined"}`).fontColor('#e6000000')
434      Text(` has ${this.book ? this.book.pages : "Book is undefined"} pages!`).fontColor('#e6000000')
435      Text(` ${this.book ? this.book.readIt ? "I have read" : 'I have not read it' : "Book is undefined"}`).fontColor('#e6000000')
436        .onClick(() => this.book.readIt = true)
437    }
438  }
439}
440
441@Entry
442@Component
443struct Library {
444  @State allBooks: Book[] = [new Book("C#", 765), new Book("JS", 652), new Book("TS", 765)];
445
446  build() {
447    Column() {
448      Text('library`s all time favorite')
449        .width(312)
450        .height(40)
451        .backgroundColor('#0d000000')
452        .borderRadius(20)
453        .margin(12)
454        .padding({ left: 20 })
455        .fontColor('#e6000000')
456      ReaderComp({ book: this.allBooks[2] })
457        .backgroundColor('#0d000000')
458        .width(312)
459        .height(40)
460        .padding({ left: 20, top: 10 })
461        .borderRadius(20)
462        .colorBlend('#e6000000')
463      Divider()
464      Text('Books on loan to a reader')
465        .width(312)
466        .height(40)
467        .backgroundColor('#0d000000')
468        .borderRadius(20)
469        .margin(12)
470        .padding({ left: 20 })
471        .fontColor('#e6000000')
472      ForEach(this.allBooks, (book: Book) => {
473        ReaderComp({ book: book })
474          .margin(12)
475          .width(312)
476          .height(40)
477          .padding({ left: 20, top: 10 })
478          .backgroundColor('#0d000000')
479          .borderRadius(20)
480      },
481        (book: Book) => book.id.toString())
482      Button('Add new')
483        .width(312)
484        .height(40)
485        .margin(12)
486        .fontColor('#FFFFFF 90%')
487        .onClick(() => {
488          this.allBooks.push(new Book("JA", 512));
489        })
490      Button('Remove first book')
491        .width(312)
492        .height(40)
493        .margin(12)
494        .fontColor('#FFFFFF 90%')
495        .onClick(() => {
496          if (this.allBooks.length > 0){
497            this.allBooks.shift();
498          } else {
499            console.log("length <= 0");
500          }
501        })
502      Button("Mark read for everyone")
503        .width(312)
504        .height(40)
505        .margin(12)
506        .fontColor('#FFFFFF 90%')
507        .onClick(() => {
508          this.allBooks.forEach((book) => book.readIt = true)
509        })
510    }
511  }
512}
513```
514
515 To observe the property of the **Book** object, you must use \@Observed to decorate the **Book** class. Note that the \@Prop decorated state variable in the child component is synchronized from the data source of the parent component in uni-directional manner. This means that, the changes of the \@Prop decorated **book** in **ReaderComp** are not synchronized to the parent **library** component. The parent component triggers UI re-rendering only when the value is updated (compared with the last state).
516
517```ts
518@Observed
519class Book {
520  public id: number;
521  public title: string;
522  public pages: number;
523  public readIt: boolean = false;
524
525  constructor(title: string, pages: number) {
526    this.id = nextId++;
527    this.title = title;
528    this.pages = pages;
529  }
530}
531```
532
533All instances of the \@Observed decorated class are wrapped with an opaque proxy object. This proxy can detect all property changes inside the wrapped object. If any property change happens, the proxy notifies the \@Prop, and the \@Prop value will be updated.
534
535![Video-prop-UsageScenario-one](figures/Video-prop-UsageScenario-one.gif)
536
537### Simple Type @Prop with Local Initialization and No Sync from Parent Component
538
539To enable an \@Component decorated component to be reusable, \@Prop allows for optional local initialization. This makes the synchronization with a variable in the parent component a choice, rather than mandatory. Providing a data source in the parent component is optional only when local initialization is provided for the \@Prop decorated variable.
540
541The following example includes two @Prop decorated variables in the child component.
542
543- **customCounter** is not initialized locally. Therefore, the parent component needs to provide the data source to deinitialize \@Prop. When the data source of the parent component changes, \@Prop is updated.
544
545- **customCounter2** has local initialization. In this case, specifying a synchronization source in the parent component is allowed but not mandatory.
546
547
548```ts
549@Component
550struct MyComponent {
551  @Prop customCounter: number;
552  @Prop customCounter2: number = 5;
553
554  build() {
555    Column() {
556      Row() {
557        Text(`From Main: ${this.customCounter}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 })
558      }
559
560      Row() {
561        Button('Click to change locally !')
562          .width(288)
563          .height(40)
564          .margin({ left: 30, top: 12 })
565          .fontColor('#FFFFFF, 90%')
566          .onClick(() => {
567            this.customCounter2++;
568          })
569      }
570
571      Row() {
572        Text(`Custom Local: ${this.customCounter2}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 })
573      }
574    }
575  }
576}
577
578@Entry
579@Component
580struct MainProgram {
581  @State mainCounter: number = 10;
582
583  build() {
584    Column() {
585      Row() {
586        Column() {
587          // customCounter must be initialized from the parent component due to lack of local initialization. Here, customCounter2 does not need to be initialized.
588          MyComponent({ customCounter: this.mainCounter })
589          // customCounter2 of the child component can also be initialized from the parent component. The value from the parent component overwrites the locally assigned value of customCounter2 during initialization.
590          MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter })
591        }
592      }
593
594      Row() {
595        Column() {
596          Button('Click to change number')
597            .width(288)
598            .height(40)
599            .margin({ left: 30, top: 12 })
600            .fontColor('#FFFFFF, 90%')
601            .onClick(() => {
602              this.mainCounter++;
603            })
604        }
605      }
606    }
607  }
608}
609```
610
611![Video-prop-UsageScenario-two](figures/Video-prop-UsageScenario-two.gif)
612
613### \@Prop Nesting Scenario
614
615In nesting scenario, each layer must be decorated with @Observed, and each layer must be received by @Prop. In this way, changes can be observed.
616
617```ts
618// The following is the data structure of a nested class object.
619@Observed
620class Son {
621  public title: string;
622
623  constructor(title: string) {
624    this.title = title;
625  }
626}
627
628@Observed
629class Father {
630  public name: string;
631  public son: Son;
632
633  constructor(name: string, son: Son) {
634    this.name = name;
635    this.son = son;
636  }
637}
638```
639
640The following component hierarchy presents a data structure of nested @Prop.
641
642```ts
643@Entry
644@Component
645struct Person {
646  @State person: Father = new Father('Hello', new Son('world'));
647
648  build() {
649    Column() {
650      Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
651        Button('change Father name')
652          .width(312)
653          .height(40)
654          .margin(12)
655          .fontColor('#FFFFFF, 90%')
656          .onClick(() => {
657            this.person.name = "Hi";
658          })
659        Button('change Son title')
660          .width(312)
661          .height(40)
662          .margin(12)
663          .fontColor('#FFFFFF, 90%')
664          .onClick(() => {
665            this.person.son.title = "ArkUI";
666          })
667        Text(this.person.name)
668          .fontSize(16)
669          .margin(12)
670          .width(312)
671          .height(40)
672          .backgroundColor('#ededed')
673          .borderRadius(20)
674          .textAlign(TextAlign.Center)
675          .fontColor('#e6000000')
676          .onClick(() => {
677            this.person.name = 'Bye';
678          })
679        Text(this.person.son.title)
680          .fontSize(16)
681          .margin(12)
682          .width(312)
683          .height(40)
684          .backgroundColor('#ededed')
685          .borderRadius(20)
686          .textAlign(TextAlign.Center)
687          .onClick(() => {
688            this.person.son.title = "openHarmony";
689          })
690        Child({ child: this.person.son })
691      }
692
693    }
694
695  }
696}
697
698
699@Component
700struct Child {
701  @Prop child: Son = new Son('');
702
703  build() {
704    Column() {
705      Text(this.child.title)
706        .fontSize(16)
707        .margin(12)
708        .width(312)
709        .height(40)
710        .backgroundColor('#ededed')
711        .borderRadius(20)
712        .textAlign(TextAlign.Center)
713        .onClick(() => {
714          this.child.title = 'Bye Bye';
715        })
716    }
717  }
718}
719```
720
721![Video-prop-UsageScenario-three](figures/Video-prop-UsageScenario-three.gif)
722
723### Decorating Variables of the Map Type
724
725> **NOTE**
726>
727> Since API version 11, \@Prop supports the Map type.
728
729In this example, the **value** variable is of the Map<number, string> type. When the button is clicked, the value of **message** changes, and the UI is re-rendered.
730
731```ts
732@Component
733struct Child {
734  @Prop value: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]);
735
736  build() {
737    Column() {
738      ForEach(Array.from(this.value.entries()), (item: [number, string]) => {
739        Text(`${item[0]}`).fontSize(30)
740        Text(`${item[1]}`).fontSize(30)
741        Divider()
742      })
743      Button('child init map').onClick(() => {
744        this.value = new Map([[0, "a"], [1, "b"], [3, "c"]]);
745      })
746      Button('child set new one').onClick(() => {
747        this.value.set(4, "d");
748      })
749      Button('child clear').onClick(() => {
750        this.value.clear();
751      })
752      Button('child replace the first one').onClick(() => {
753        this.value.set(0, "aa");
754      })
755      Button('child delete the first one').onClick(() => {
756        this.value.delete(0);
757      })
758    }
759  }
760}
761
762
763@Entry
764@Component
765struct MapSample {
766  @State message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]);
767
768  build() {
769    Row() {
770      Column() {
771        Child({ value: this.message })
772      }
773      .width('100%')
774    }
775    .height('100%')
776  }
777}
778```
779
780### Decorating Variables of the Set Type
781
782> **NOTE**
783>
784> Since API version 11, \@Prop supports the Set type.
785
786In this example, the **message** variable is of the **Set\<number\>** type. When the button is clicked, the value of **message** changes, and the UI is re-rendered.
787
788```ts
789@Component
790struct Child {
791  @Prop message: Set<number> = new Set([0, 1, 2, 3, 4]);
792
793  build() {
794    Column() {
795      ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
796        Text(`${item[0]}`).fontSize(30)
797        Divider()
798      })
799      Button('init set').onClick(() => {
800        this.message = new Set([0, 1, 2, 3, 4]);
801      })
802      Button('set new one').onClick(() => {
803        this.message.add(5);
804      })
805      Button('clear').onClick(() => {
806        this.message.clear();
807      })
808      Button('delete the first one').onClick(() => {
809        this.message.delete(0);
810      })
811    }
812    .width('100%')
813  }
814}
815
816
817@Entry
818@Component
819struct SetSample {
820  @State message: Set<number> = new Set([0, 1, 2, 3, 4]);
821
822  build() {
823    Row() {
824      Column() {
825        Child({ message: this.message })
826      }
827      .width('100%')
828    }
829    .height('100%')
830  }
831}
832```
833
834## Union Type @Prop
835
836@Prop supports **undefined**, **null**, and union types. In the following example, the type of **animal** is **Animals | undefined**. If the property or type of **animal** is changed when the button in the parent component **Zoo** is clicked, the change will be synced to the child component.
837
838```ts
839class Animals {
840  public name: string;
841
842  constructor(name: string) {
843    this.name = name;
844  }
845}
846
847@Component
848struct Child {
849  @Prop animal: Animals | undefined;
850
851  build() {
852    Column() {
853      Text(`Child's animal is  ${this.animal instanceof Animals ? this.animal.name : 'undefined'}`).fontSize(30)
854
855      Button('Child change animals into tigers')
856        .onClick(() => {
857          // Assign the value of an instance of Animals.
858          this.animal = new Animals("Tiger");
859        })
860
861      Button('Child change animal to undefined')
862        .onClick(() => {
863          // Assign the value undefined.
864          this.animal = undefined;
865        })
866
867    }.width('100%')
868  }
869}
870
871@Entry
872@Component
873struct Zoo {
874  @State animal: Animals | undefined = new Animals("lion");
875
876  build() {
877    Column() {
878      Text(`Parents' animals are  ${this.animal instanceof Animals ? this.animal.name : 'undefined'}`).fontSize(30)
879
880      Child({animal: this.animal})
881
882      Button('Parents change animals into dogs')
883        .onClick(() => {
884          // Determine the animal type and update the property.
885          if (this.animal instanceof Animals) {
886            this.animal.name = "Dog";
887          } else {
888            console.info('num is undefined, cannot change property');
889          }
890        })
891
892      Button('Parents change animal to undefined')
893        .onClick(() => {
894          // Assign the value undefined.
895          this.animal = undefined;
896        })
897    }
898  }
899}
900```
901
902
903## FAQs
904
905### \@Prop Decorated State Variable Not Initialized
906
907The \@Prop decorated state variable must be initialized. If not initialized locally, the variable must be initialized from the parent component. If it has been initialized locally, initialization from the parent component is optional.
908
909[Incorrect Example]
910
911```ts
912@Observed
913class Commodity {
914  public price: number = 0;
915
916  constructor(price: number) {
917    this.price = price;
918  }
919}
920
921@Component
922struct PropChild {
923  @Prop fruit: Commodity; // The state variable is not initialized locally.
924
925  build() {
926    Text(`PropChild fruit ${this.fruit.price}`)
927      .onClick(() => {
928        this.fruit.price += 1;
929      })
930  }
931}
932
933@Entry
934@Component
935struct Parent {
936  @State fruit: Commodity[] = [new Commodity(1)];
937
938  build() {
939    Column() {
940      Text(`Parent fruit ${this.fruit[0].price}`)
941        .onClick(() => {
942          this.fruit[0].price += 1;
943        })
944
945      // The @Prop state variable is not initialized locally, nor initialized from the parent component.
946      PropChild()
947    }
948  }
949}
950```
951
952[Correct Example]
953
954```ts
955@Observed
956class Commodity {
957  public price: number = 0;
958
959  constructor(price: number) {
960    this.price = price;
961  }
962}
963
964@Component
965struct PropChild1 {
966  @Prop fruit: Commodity; // The state variable is not initialized locally.
967
968  build() {
969    Text(`PropChild1 fruit ${this.fruit.price}`)
970      .onClick(() => {
971        this.fruit.price += 1;
972      })
973  }
974}
975
976@Component
977struct PropChild2 {
978  @Prop fruit: Commodity = new Commodity(1); // The state variable is initialized locally.
979
980  build() {
981    Text(`PropChild2 fruit ${this.fruit.price}`)
982      .onClick(() => {
983        this.fruit.price += 1;
984      })
985  }
986}
987
988@Entry
989@Component
990struct Parent {
991  @State fruit: Commodity[] = [new Commodity(1)];
992
993  build() {
994    Column() {
995      Text(`Parent fruit ${this.fruit[0].price}`)
996        .onClick(() => {
997          this.fruit[0].price += 1;
998        })
999
1000      // @PropChild1 is not initialized locally and must be initialized from the parent component.
1001      PropChild1({ fruit: this.fruit[0] })
1002      // @PropChild2 is initialized locally. In this case, initialization from the parent component is optional.
1003      PropChild2()
1004      PropChild2({ fruit: this.fruit[0] })
1005    }
1006  }
1007}
1008```
1009
1010### Using the a.b(this.object) Format Fails to Trigger UI Re-render
1011
1012In the **build** method, when the variable decorated by @Prop is of the object type and is called using the **a.b(this.object)** format, the original object of **this.object** is passed in the b method. If the property of **this.object** is changed, the UI cannot be re-rendered. In the following example, when the static method **Score.changeScore1** or **this.changeScore2** is used to change **this.score.value** in the custom component **Child**, the UI is not re-rendered.
1013
1014[Incorrect Example]
1015
1016```ts
1017class Score {
1018  value: number;
1019  constructor(value: number) {
1020    this.value = value;
1021  }
1022
1023  static changeScore1(param1:Score) {
1024    param1.value += 1;
1025  }
1026}
1027
1028@Entry
1029@Component
1030struct Parent {
1031  @State score: Score = new Score(1);
1032
1033  build() {
1034    Column({space:8}) {
1035      Text(`The value in Parent is ${this.score.value}.`)
1036        .fontSize(30)
1037        .fontColor(Color.Red)
1038      Child({ score: this.score })
1039    }
1040    .width('100%')
1041    .height('100%')
1042  }
1043}
1044
1045@Component
1046struct Child {
1047  @Prop score: Score;
1048
1049  changeScore2(param2:Score) {
1050    param2.value += 2;
1051  }
1052
1053  build() {
1054    Column({space:8}) {
1055      Text(`The value in Child is ${this.score.value}.`)
1056        .fontSize(30)
1057      Button(`changeScore1`)
1058        .onClick(()=>{
1059          // The UI cannot be re-rendered using a static method.
1060          Score.changeScore1(this.score);
1061        })
1062      Button(`changeScore2`)
1063        .onClick(()=>{
1064          // The UI cannot be re-rendered using this.
1065          this.changeScore2(this.score);
1066        })
1067    }
1068  }
1069}
1070```
1071
1072You can add a proxy for **this.score** to re-render the UI by assigning a value to the variable and then calling the variable.
1073
1074[Correct Example]
1075
1076```ts
1077class Score {
1078  value: number;
1079  constructor(value: number) {
1080    this.value = value;
1081  }
1082
1083  static changeScore1(score:Score) {
1084    score.value += 1;
1085  }
1086}
1087
1088@Entry
1089@Component
1090struct Parent {
1091  @State score: Score = new Score(1);
1092
1093  build() {
1094    Column({space:8}) {
1095      Text(`The value in Parent is ${this.score.value}.`)
1096        .fontSize(30)
1097        .fontColor(Color.Red)
1098      Child({ score: this.score })
1099    }
1100    .width('100%')
1101    .height('100%')
1102  }
1103}
1104
1105@Component
1106struct Child {
1107  @Prop score: Score;
1108
1109  changeScore2(score:Score) {
1110    score.value += 2;
1111  }
1112
1113  build() {
1114    Column({space:8}) {
1115      Text(`The value in Child is ${this.score.value}.`)
1116        .fontSize(30)
1117      Button(`changeScore1`)
1118        .onClick(()=>{
1119          // Add a proxy by assigning a value.
1120          let score1 = this.score;
1121          Score.changeScore1(score1);
1122        })
1123      Button(`changeScore2`)
1124        .onClick(()=>{
1125          // Add a proxy by assigning a value.
1126          let score2 = this.score;
1127          this.changeScore2(score2);
1128        })
1129    }
1130  }
1131}
1132```
1133
1134