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## Restrictions 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- The \@Prop decorator cannot be used in custom components decorated by \@Entry. 27 28 29## Rules of Use 30 31| \@Prop Decorator| Description | 32| ----------- | ---------------------------------------- | 33| Decorator parameters | None. | 34| 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).| 35| 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 recommended; **@Prop a: string = undefined** is not recommended.| 36| 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.| 37| 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.| 38 39 40## Variable Transfer/Access Rules 41 42| Transfer/Access | Description | 43| ------------------ | ------------------------------------------------------------ | 44| 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.| 45| 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.| 46| Access| Private, accessible only within the component. | 47 48 49 **Figure 1** Initialization rule 50 51 52 53 54 55## Observed Changes and Behavior 56 57 58### Observed Changes 59 60\@Prop decorated variables can observe the following changes: 61 62- When the decorated variable is of the Object, class, string, number, Boolean, or enum type, its value change can be observed. 63 64 ```ts 65 // Simple type 66 @Prop count: number; 67 // The value change can be observed. 68 this.count = 1; 69 // Complex type 70 @Prop title: Model; 71 // The value change can be observed. 72 this.title = new Model('Hi'); 73 ``` 74 75- 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. 76 77```ts 78class Info { 79 public value: string; 80 constructor(value: string) { 81 this.value = value; 82 } 83} 84class Model { 85 public value: string; 86 public info: Info; 87 constructor(value: string, info: Info) { 88 this.value = value; 89 this.info = info; 90 } 91} 92 93@Prop title: Model; 94// The value changes at the first layer can be observed. 95this.title.value = 'Hi'; 96// The value changes at the second layer cannot be observed. 97this.title.info.value = 'ArkUI'; 98``` 99 100In 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). 101 102- 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. 103 104```ts 105// Assume that the object decorated by @State is an array. 106@Prop title: string[]; 107// The value change of the array itself can be observed. 108this.title = ['1']; 109// The value change of array items can be observed. 110this.title[0] = '2'; 111// The deletion of array items can be observed. 112this.title.pop(); 113// The addition of array items can be observed. 114this.title.push('3'); 115``` 116 117For synchronization between \@State and \@Prop decorated variables: 118 119- 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. 120- However, any change to the @Prop decorated variable does not affect the value of its source @State decorated variable. 121- 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. 122- The source and \@Prop decorated variable must be of the same type. The \@Prop decorated variable can be of simple and class types. 123 124- 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**. 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- 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). 181 182- 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). 183 184### Framework Behavior 185 186To 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. 187 1881. Initial render: 189 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. 190 2. The @Prop decorated variable is initialized. 191 1922. Update: 193 1. When the @Prop decorated variable is modified locally, the change does not propagate back to its parent component. 194 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. 195 196> **NOTE** 197> 198> 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. 199 200 201## Usage Scenarios 202 203 204### Simple Type Sync from @State of the Parent Component to @Prop of the Child Component 205 206 207In 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**. 208 209 210Updating **countDownStartValue** in the **ParentComponent** will update the value of the @Prop decorated **count**. 211 212```ts 213@Component 214struct CountDownComponent { 215 @Prop count: number = 0; 216 costOfOneAttempt: number = 1; 217 218 build() { 219 Column() { 220 if (this.count > 0) { 221 Text(`You have ${this.count} Nuggets left`) 222 } else { 223 Text('Game over!') 224 } 225 // Changes to the @Prop decorated variables are not synchronized to the parent component. 226 Button(`Try again`).onClick(() => { 227 this.count -= this.costOfOneAttempt; 228 }) 229 } 230 } 231} 232 233@Entry 234@Component 235struct ParentComponent { 236 @State countDownStartValue: number = 10; 237 238 build() { 239 Column() { 240 Text(`Grant ${this.countDownStartValue} nuggets to play.`) 241 // Changes to the data source provided by the parent component are synchronized to the child component. 242 Button(`+1 - Nuggets in New Game`).onClick(() => { 243 this.countDownStartValue += 1; 244 }) 245 // Updating the parent component will also update the child component. 246 Button(`-1 - Nuggets in New Game`).onClick(() => { 247 this.countDownStartValue -= 1; 248 }) 249 250 CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 }) 251 } 252 } 253} 254``` 255 256 257In the preceding example: 258 259 2601. 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**. 261 2622. 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. 263 2643. 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. 265 2664. When **Try again** in the **CountDownComponent** child component is touched, the value of the **count** variable is modified, but the change remains within the child component and does not affect the **countDownStartValue** in the parent component. 267 2685. Updating **countDownStartValue** will overwrite the local value changes of the @Prop decorated **count** in the **CountDownComponent** child component. 269 270 271### Simple Type @Prop Synced from @State Array Item in Parent Component 272 273 274The \@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**. 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: number) => item.toString() 309 ) 310 Text('replace entire arr') 311 .fontSize(50) 312 .onClick(() => { 313 // Both arrays contain item "3". 314 this.arr = this.arr[0] == 1 ? [3, 4, 5] : [1, 2, 3]; 315 }) 316 } 317 } 318 } 319} 320``` 321 322 323Initial 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. 324 325 326Click **1** six times, 2 five times, and **3** four times on the page. The local values of all variables are then changed to **7**. 327 328``` 3297 3307 3317 332---- 3337 3347 3357 336``` 337 338 339After **replace entire arr** is clicked, the following information is displayed: 340 341``` 3423 3434 3445 345---- 3467 3474 3485 349``` 350 351 352- 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]**. 353 354- After **replace entire arr** is clicked, if **this.arr[0] == 1** is true, **this.arr** is set to **[3, 4, 5]**. 355 356- 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]})**. 357 358 359- 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**. 360 361 362### Class Object Type @Prop Synced from @State Class Object Property in Parent Component 363 364In 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. 365 366In 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). 367 368 369```ts 370class Book { 371 public title: string; 372 public pages: number; 373 public readIt: boolean = false; 374 375 constructor(title: string, pages: number) { 376 this.title = title; 377 this.pages = pages; 378 } 379} 380 381@Component 382struct ReaderComp { 383 @Prop book: Book = new Book("", 0); 384 385 build() { 386 Row() { 387 Text(this.book.title) 388 Text(`...has${this.book.pages} pages!`) 389 Text(`...${this.book.readIt ? "I have read" : 'I have not read it'}`) 390 .onClick(() => this.book.readIt = true) 391 } 392 } 393} 394 395@Entry 396@Component 397struct Library { 398 @State book: Book = new Book('100 secrets of C++', 765); 399 400 build() { 401 Column() { 402 ReaderComp({ book: this.book }) 403 ReaderComp({ book: this.book }) 404 } 405 } 406} 407``` 408 409### Class Type @Prop Synced from @State Array Item in Parent Component 410 411In 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**. 412 413```ts 414let nextId: number = 1; 415 416// @Observed 417class Book { 418 public id: number; 419 public title: string; 420 public pages: number; 421 public readIt: boolean = false; 422 423 constructor(title: string, pages: number) { 424 this.id = nextId++; 425 this.title = title; 426 this.pages = pages; 427 } 428} 429 430@Component 431struct ReaderComp { 432 @Prop book: Book = new Book("", 1); 433 434 build() { 435 Row() { 436 Text(` ${this.book ? this.book.title : "Book is undefined"}`).fontColor('#e6000000') 437 Text(` has ${this.book ? this.book.pages : "Book is undefined"} pages!`).fontColor('#e6000000') 438 Text(` ${this.book ? this.book.readIt ? "I have read" : 'I have not read it' : "Book is undefined"}`).fontColor('#e6000000') 439 .onClick(() => this.book.readIt = true) 440 } 441 } 442} 443 444@Entry 445@Component 446struct Library { 447 @State allBooks: Book[] = [new Book("C#", 765), new Book("JS", 652), new Book("TS", 765)]; 448 449 build() { 450 Column() { 451 Text('library`s all time favorite') 452 .width(312) 453 .height(40) 454 .backgroundColor('#0d000000') 455 .borderRadius(20) 456 .margin(12) 457 .padding({ left: 20 }) 458 .fontColor('#e6000000') 459 ReaderComp({ book: this.allBooks[2] }) 460 .backgroundColor('#0d000000') 461 .width(312) 462 .height(40) 463 .padding({ left: 20, top: 10 }) 464 .borderRadius(20) 465 .colorBlend('#e6000000') 466 Divider() 467 Text('Books on loan to a reader') 468 .width(312) 469 .height(40) 470 .backgroundColor('#0d000000') 471 .borderRadius(20) 472 .margin(12) 473 .padding({ left: 20 }) 474 .fontColor('#e6000000') 475 ForEach(this.allBooks, (book: Book) => { 476 ReaderComp({ book: book }) 477 .margin(12) 478 .width(312) 479 .height(40) 480 .padding({ left: 20, top: 10 }) 481 .backgroundColor('#0d000000') 482 .borderRadius(20) 483 }, 484 (book: Book) => book.id.toString()) 485 Button('Add new') 486 .width(312) 487 .height(40) 488 .margin(12) 489 .fontColor('#FFFFFF 90%') 490 .onClick(() => { 491 this.allBooks.push(new Book("JA", 512)); 492 }) 493 Button('Remove first book') 494 .width(312) 495 .height(40) 496 .margin(12) 497 .fontColor('#FFFFFF 90%') 498 .onClick(() => { 499 if (this.allBooks.length > 0){ 500 this.allBooks.shift(); 501 } else { 502 console.log("length <= 0"); 503 } 504 }) 505 Button("Mark read for everyone") 506 .width(312) 507 .height(40) 508 .margin(12) 509 .fontColor('#FFFFFF 90%') 510 .onClick(() => { 511 this.allBooks.forEach((book) => book.readIt = true) 512 }) 513 } 514 } 515} 516``` 517 518 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). 519 520```ts 521@Observed 522class Book { 523 public id: number; 524 public title: string; 525 public pages: number; 526 public readIt: boolean = false; 527 528 constructor(title: string, pages: number) { 529 this.id = nextId++; 530 this.title = title; 531 this.pages = pages; 532 } 533} 534``` 535 536All 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. 537 538 539 540### Simple Type @Prop with Local Initialization and No Sync from Parent Component 541 542To 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. 543 544The following example includes two @Prop decorated variables in the child component. 545 546- The @Prop decorated variable **customCounter** has no local initialization, and therefore it requires a synchronization source in its parent component. When the source value changes, the @Prop decorated variable is updated. 547 548- The @Prop decorated variable **customCounter2** has local initialization. In this case, specifying a synchronization source in the parent component is allowed but not mandatory. 549 550 551```ts 552@Component 553struct MyComponent { 554 @Prop customCounter: number; 555 @Prop customCounter2: number = 5; 556 557 build() { 558 Column() { 559 Row() { 560 Text(`From Main: ${this.customCounter}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 }) 561 } 562 563 Row() { 564 Button('Click to change locally !') 565 .width(288) 566 .height(40) 567 .margin({ left: 30, top: 12 }) 568 .fontColor('#FFFFFF, 90%') 569 .onClick(() => { 570 this.customCounter2++; 571 }) 572 } 573 574 Row() { 575 Text(`Custom Local: ${this.customCounter2}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 }) 576 } 577 } 578 } 579} 580 581@Entry 582@Component 583struct MainProgram { 584 @State mainCounter: number = 10; 585 586 build() { 587 Column() { 588 Row() { 589 Column() { 590 // customCounter must be initialized from the parent component due to lack of local initialization. Here, customCounter2 does not need to be initialized. 591 MyComponent({ customCounter: this.mainCounter }) 592 // 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. 593 MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter }) 594 } 595 } 596 597 Row() { 598 Column() { 599 Button('Click to change number') 600 .width(288) 601 .height(40) 602 .margin({ left: 30, top: 12 }) 603 .fontColor('#FFFFFF, 90%') 604 .onClick(() => { 605 this.mainCounter++; 606 }) 607 } 608 } 609 } 610 } 611} 612``` 613 614 615 616### \@Prop Nesting Scenario 617 618In nesting scenario, each layer must be decorated with @Observed, and each layer must be received by @Prop. In this way, changes can be observed. 619 620```ts 621// The following is the data structure of a nested class object. 622@Observed 623class Son { 624 public title: string; 625 626 constructor(title: string) { 627 this.title = title; 628 } 629} 630 631@Observed 632class Father { 633 public name: string; 634 public son: Son; 635 636 constructor(name: string, son: Son) { 637 this.name = name; 638 this.son = son; 639 } 640} 641``` 642 643The following component hierarchy presents a data structure of nested @Prop. 644 645```ts 646@Entry 647@Component 648struct Person { 649 @State person: Father = new Father('Hello', new Son('world')); 650 651 build() { 652 Column() { 653 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) { 654 Button('change Father name') 655 .width(312) 656 .height(40) 657 .margin(12) 658 .fontColor('#FFFFFF, 90%') 659 .onClick(() => { 660 this.person.name = "Hi"; 661 }) 662 Button('change Son title') 663 .width(312) 664 .height(40) 665 .margin(12) 666 .fontColor('#FFFFFF, 90%') 667 .onClick(() => { 668 this.person.son.title = "ArkUI"; 669 }) 670 Text(this.person.name) 671 .fontSize(16) 672 .margin(12) 673 .width(312) 674 .height(40) 675 .backgroundColor('#ededed') 676 .borderRadius(20) 677 .textAlign(TextAlign.Center) 678 .fontColor('#e6000000') 679 .onClick(() => { 680 this.person.name = 'Bye'; 681 }) 682 Text(this.person.son.title) 683 .fontSize(16) 684 .margin(12) 685 .width(312) 686 .height(40) 687 .backgroundColor('#ededed') 688 .borderRadius(20) 689 .textAlign(TextAlign.Center) 690 .onClick(() => { 691 this.person.son.title = "openHarmony"; 692 }) 693 Child({ child: this.person.son }) 694 } 695 696 } 697 698 } 699} 700 701 702@Component 703struct Child { 704 @Prop child: Son = new Son(''); 705 706 build() { 707 Column() { 708 Text(this.child.title) 709 .fontSize(16) 710 .margin(12) 711 .width(312) 712 .height(40) 713 .backgroundColor('#ededed') 714 .borderRadius(20) 715 .textAlign(TextAlign.Center) 716 .onClick(() => { 717 this.child.title = 'Bye Bye'; 718 }) 719 } 720 } 721} 722``` 723 724 725 726### Decorating Variables of the Map Type 727 728> **NOTE** 729> 730> Since API version 11, \@Prop supports the Map type. 731 732In 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. 733 734```ts 735@Component 736struct Child { 737 @Prop value: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]); 738 739 build() { 740 Column() { 741 ForEach(Array.from(this.value.entries()), (item: [number, string]) => { 742 Text(`${item[0]}`).fontSize(30) 743 Text(`${item[1]}`).fontSize(30) 744 Divider() 745 }) 746 Button('child init map').onClick(() => { 747 this.value = new Map([[0, "a"], [1, "b"], [3, "c"]]); 748 }) 749 Button('child set new one').onClick(() => { 750 this.value.set(4, "d"); 751 }) 752 Button('child clear').onClick(() => { 753 this.value.clear(); 754 }) 755 Button('child replace the first one').onClick(() => { 756 this.value.set(0, "aa"); 757 }) 758 Button('child delete the first one').onClick(() => { 759 this.value.delete(0); 760 }) 761 } 762 } 763} 764 765 766@Entry 767@Component 768struct MapSample { 769 @State message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]); 770 771 build() { 772 Row() { 773 Column() { 774 Child({ value: this.message }) 775 } 776 .width('100%') 777 } 778 .height('100%') 779 } 780} 781``` 782 783### Decorating Variables of the Set Type 784 785> **NOTE** 786> 787> Since API version 11, \@Prop supports the Set type. 788 789In 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. 790 791```ts 792@Component 793struct Child { 794 @Prop message: Set<number> = new Set([0, 1, 2, 3, 4]); 795 796 build() { 797 Column() { 798 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 799 Text(`${item[0]}`).fontSize(30) 800 Divider() 801 }) 802 Button('init set').onClick(() => { 803 this.message = new Set([0, 1, 2, 3, 4]); 804 }) 805 Button('set new one').onClick(() => { 806 this.message.add(5); 807 }) 808 Button('clear').onClick(() => { 809 this.message.clear(); 810 }) 811 Button('delete the first one').onClick(() => { 812 this.message.delete(0); 813 }) 814 } 815 .width('100%') 816 } 817} 818 819 820@Entry 821@Component 822struct SetSample { 823 @State message: Set<number> = new Set([0, 1, 2, 3, 4]); 824 825 build() { 826 Row() { 827 Column() { 828 Child({ message: this.message }) 829 } 830 .width('100%') 831 } 832 .height('100%') 833 } 834} 835``` 836 837## Union Type @Prop 838 839@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. 840 841```ts 842class Animals { 843 public name: string; 844 845 constructor(name: string) { 846 this.name = name; 847 } 848} 849 850@Component 851struct Child { 852 @Prop animal: Animals | undefined; 853 854 build() { 855 Column() { 856 Text(`Child's animal is ${this.animal instanceof Animals ? this.animal.name : 'undefined'}`).fontSize(30) 857 858 Button('Child change animals into tigers') 859 .onClick(() => { 860 // Assign the value of an instance of Animals. 861 this.animal = new Animals("Tiger"); 862 }) 863 864 Button('Child change animal to undefined') 865 .onClick(() => { 866 // Assign the value undefined. 867 this.animal = undefined; 868 }) 869 870 }.width('100%') 871 } 872} 873 874@Entry 875@Component 876struct Zoo { 877 @State animal: Animals | undefined = new Animals("lion"); 878 879 build() { 880 Column() { 881 Text(`Parents' animals are ${this.animal instanceof Animals ? this.animal.name : 'undefined'}`).fontSize(30) 882 883 Child({animal: this.animal}) 884 885 Button('Parents change animals into dogs') 886 .onClick(() => { 887 // Determine the animal type and update the property. 888 if (this.animal instanceof Animals) { 889 this.animal.name = "Dog"; 890 } else { 891 console.info('num is undefined, cannot change property'); 892 } 893 }) 894 895 Button('Parents change animal to undefined') 896 .onClick(() => { 897 // Assign the value undefined. 898 this.animal = undefined; 899 }) 900 } 901 } 902} 903``` 904 905 906## FAQs 907 908### \@Prop Decorated State Variable Not Initialized 909 910The \@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. 911 912[Incorrect Example] 913 914```ts 915@Observed 916class Commodity { 917 public price: number = 0; 918 919 constructor(price: number) { 920 this.price = price; 921 } 922} 923 924@Component 925struct PropChild { 926 @Prop fruit: Commodity; // The state variable is not initialized locally. 927 928 build() { 929 Text(`PropChild fruit ${this.fruit.price}`) 930 .onClick(() => { 931 this.fruit.price += 1; 932 }) 933 } 934} 935 936@Entry 937@Component 938struct Parent { 939 @State fruit: Commodity[] = [new Commodity(1)]; 940 941 build() { 942 Column() { 943 Text(`Parent fruit ${this.fruit[0].price}`) 944 .onClick(() => { 945 this.fruit[0].price += 1; 946 }) 947 948 // The @Prop state variable is not initialized locally, nor initialized from the parent component. 949 PropChild() 950 } 951 } 952} 953``` 954 955[Correct Example] 956 957```ts 958@Observed 959class Commodity { 960 public price: number = 0; 961 962 constructor(price: number) { 963 this.price = price; 964 } 965} 966 967@Component 968struct PropChild1 { 969 @Prop fruit: Commodity; // The state variable is not initialized locally. 970 971 build() { 972 Text(`PropChild1 fruit ${this.fruit.price}`) 973 .onClick(() => { 974 this.fruit.price += 1; 975 }) 976 } 977} 978 979@Component 980struct PropChild2 { 981 @Prop fruit: Commodity = new Commodity(1); // The state variable is initialized locally. 982 983 build() { 984 Text(`PropChild2 fruit ${this.fruit.price}`) 985 .onClick(() => { 986 this.fruit.price += 1; 987 }) 988 } 989} 990 991@Entry 992@Component 993struct Parent { 994 @State fruit: Commodity[] = [new Commodity(1)]; 995 996 build() { 997 Column() { 998 Text(`Parent fruit ${this.fruit[0].price}`) 999 .onClick(() => { 1000 this.fruit[0].price += 1; 1001 }) 1002 1003 // @PropChild1 is not initialized locally and must be initialized from the parent component. 1004 PropChild1({ fruit: this.fruit[0] }) 1005 // @PropChild2 is initialized locally. In this case, initialization from the parent component is optional. 1006 PropChild2() 1007 PropChild2({ fruit: this.fruit[0] }) 1008 } 1009 } 1010} 1011``` 1012 1013### Using the a.b(this.object) Format Fails to Trigger UI Re-render 1014 1015In 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 native 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. 1016 1017[Incorrect Example] 1018 1019```ts 1020class Score { 1021 value: number; 1022 constructor(value: number) { 1023 this.value = value; 1024 } 1025 1026 static changeScore1(param1:Score) { 1027 param1.value += 1; 1028 } 1029} 1030 1031@Entry 1032@Component 1033struct Parent { 1034 @State score: Score = new Score(1); 1035 1036 build() { 1037 Column({space:8}) { 1038 Text(`The value in Parent is ${this.score.value}.`) 1039 .fontSize(30) 1040 .fontColor(Color.Red) 1041 Child({ score: this.score }) 1042 } 1043 .width('100%') 1044 .height('100%') 1045 } 1046} 1047 1048@Component 1049struct Child { 1050 @Prop score: Score; 1051 1052 changeScore2(param2:Score) { 1053 param2.value += 2; 1054 } 1055 1056 build() { 1057 Column({space:8}) { 1058 Text(`The value in Child is ${this.score.value}.`) 1059 .fontSize(30) 1060 Button(`changeScore1`) 1061 .onClick(()=>{ 1062 // The UI cannot be re-rendered using a static method. 1063 Score.changeScore1(this.score); 1064 }) 1065 Button(`changeScore2`) 1066 .onClick(()=>{ 1067 // The UI cannot be re-rendered using this. 1068 this.changeScore2(this.score); 1069 }) 1070 } 1071 } 1072} 1073``` 1074 1075You can add a proxy for **this.score** to re-render the UI by assigning a value to the variable and then calling the variable. 1076 1077[Correct Example] 1078 1079```ts 1080class Score { 1081 value: number; 1082 constructor(value: number) { 1083 this.value = value; 1084 } 1085 1086 static changeScore1(score:Score) { 1087 score.value += 1; 1088 } 1089} 1090 1091@Entry 1092@Component 1093struct Parent { 1094 @State score: Score = new Score(1); 1095 1096 build() { 1097 Column({space:8}) { 1098 Text(`The value in Parent is ${this.score.value}.`) 1099 .fontSize(30) 1100 .fontColor(Color.Red) 1101 Child({ score: this.score }) 1102 } 1103 .width('100%') 1104 .height('100%') 1105 } 1106} 1107 1108@Component 1109struct Child { 1110 @Prop score: Score; 1111 1112 changeScore2(score:Score) { 1113 score.value += 2; 1114 } 1115 1116 build() { 1117 Column({space:8}) { 1118 Text(`The value in Child is ${this.score.value}.`) 1119 .fontSize(30) 1120 Button(`changeScore1`) 1121 .onClick(()=>{ 1122 // Add a proxy by assigning a value. 1123 let score1 = this.score; 1124 Score.changeScore1(score1); 1125 }) 1126 Button(`changeScore2`) 1127 .onClick(()=>{ 1128 // Add a proxy by assigning a value. 1129 let score2 = this.score; 1130 this.changeScore2(score2); 1131 }) 1132 } 1133 } 1134} 1135``` 1136 1137