1# \@Param Decorator: Inputting External Parameters to Components 2 3 4You can use \@Param, a variable decorator in state management V2, to enhance the capability of child components to receive external parameter input. 5 6> **NOTE** 7> 8> The \@Param decorator is supported since API version 12. 9> 10>State management V2 is still under development, and some features may be incomplete or not always work as expected. 11 12## Overview 13 14\@Param indicates the state passed in from the external, ensuring that data can be synchronized between the parent and child components. 15 16- Variables decorated by \@Param can be initialized locally, but cannot be changed within the component. 17 18- \@Param decorated variables can be passed in from the external when initializing a custom component. When the data source is also a state variable, changes of the data source will be synchronized to \@Param. 19- \@Param can accept data sources of any type, including common variables, state variables, constants, and function return values. 20- When an \@Param decorated variable changes, the component associated with the variable will be re-rendered. 21- \@Param can observe basic types such as number, boolean, string, object, and class and built-in types such as Array, Set, Map, and Date. 22- For complex types such as class objects, \@Param accepts references from the data source. You can change the class object properties in the component and this change will be synchronized to the data source. 23- \@Param can only observe the decorated variables. When a simple type is decorated, the overall change of variables can be observed. When an object type is decorated, only the overall change of the object can be observed. When an array type is decorated, the overall change of the entire array and its elements can be observed. When the built-in types such as Array, Set, Map, and Date are decorated, the changes brought by API invoking can be observed. For details, see [Observed Changes](#observed-changes). 24- \@Param supports null, undefined, and union types. 25 26 27## Limitations of State Management V1 to Accept Decorators Passed in Externally 28The state management V1 has multiple decorators that can be passed in from external systems. The common-used decorators are \@State, \@Prop, \@Link, and \@ObjectLink. These decorators have their own constraints and are difficult to be distinguished. When they are used improperly, they may cause performance problems. 29 30```ts 31@Observed 32class Region { 33 x: number; 34 y: number; 35 constructor(x: number, y: number) { 36 this.x = x; 37 this.y = y; 38 } 39} 40@Observed 41class Info { 42 region: Region; 43 constructor(x: number, y: number) { 44 this.region = new Region(x, y); 45 } 46} 47@Entry 48@Component 49struct Index { 50 @State info: Info = new Info(0, 0); 51 52 build() { 53 Column() { 54 Button("change Info") 55 .onClick(() => { 56 this.info = new Info(100, 100); 57 }) 58 Child({ 59 region: this.info.region, 60 regionProp: this.info.region, 61 infoProp: this.info, 62 infoLink: this.info, 63 infoState: this.info 64 }) 65 } 66 } 67} 68@Component 69struct Child { 70 @ObjectLink region: Region; 71 @Prop regionProp: Region; 72 @Prop infoProp: Info; 73 @Link infoLink: Info; 74 @State infoState: Info = new Info(1, 1); 75 build() { 76 Column() { 77 Text(`ObjectLink region: ${this.region.x}-${this.region.y}`) 78 Text(`Prop regionProp: ${this.regionProp.x}-${this.regionProp.y}`) 79 } 80 } 81} 82``` 83 84In the preceding example, \@State can obtain the reference of **info** only during initialization. After **info** is changed, synchronization cannot be performed. \@Prop supports one-way synchronization, but the deep copy performance is still poor for complex types. \@Link can synchronize the input reference in a two-way manner, but it requires that the data source be also a state variable. Therefore, it cannot accept the member property **region** in **info**. \@ObjectLink can accept the class member property which must be decorated by \@Observed. Different constraints of the decorator make the rules for transferring values between parent and child components complex and difficult to use. This is where \@Param, a decorator that indicates the component state passed in from the external, comes into the picture. 85 86## Decorator Description 87 88| \@Param Variable Decorator | Description | 89| ------------------ | ------------------------------------------------------------ | 90| Parameter | None. | 91| Allowed local modification | None. The capability of the \@Event decorator is required to change the value. | 92| Synchronization type | One-way synchronization from the parent to the child component. | 93| Allowed variable types| Basic types such as object, class, string, number, boolean, and enum and embedded types such as Array, Date, Map, and Set. Null, undefined, and union types are supported.| 94| Initial value for the decorated variable| Local initialization is allowed. If local initialization is not performed, this parameter must be used together with the \@Require decorator and initialization must be passed in from the external.| 95 96## Variable Passing 97 98| Passing Rules | Description | 99| -------------- | ------------------------------------------------------------ | 100| Initialization from the parent component| \@Param decorated variables can be initialized locally. If local initialization does not performed, the variables must be initialized from the external. When both the local initial value and externally input value exist, the latter is preferentially used for initialization.| 101| Child component initialization | \@Param decorated variables can initialize themselves in the child components. | 102| Synchronization | \@Param can be synchronized with the state variable data source passed in by the parent component (that is, the variable decorated by \@Local or \@Param). When the data source changes, the changes will be synchronized to \@Param of the child component.| 103 104## Observed Changes 105 106\@Param decorated variables enjoys observation capability. When a decorated variable changes, the UI component bound to the variable will be re-rendered. 107 108- When the decorated variable type is boolean, string, or number, you can observe the changes synchronized from the data source. 109 110 ```ts 111 @Entry 112 @ComponentV2 113 struct Index { 114 @Local count: number = 0; 115 @Local message: string = "Hello"; 116 @Local flag: boolean = false; 117 build() { 118 Column() { 119 Text(`Local ${this.count}`) 120 Text(`Local ${this.message}`) 121 Text(`Local ${this.flag}`) 122 Button("change Local") 123 .onClick(()=>{ 124 // Changes to the data source will be synchronized to the child component. 125 this.count++; 126 this.message += " World"; 127 this.flag = !this.flag; 128 }) 129 Child({ 130 count: this.count, 131 message: this.message, 132 flag: this.flag 133 }) 134 } 135 } 136 } 137 @ComponentV2 138 struct Child { 139 @Require @Param count: number; 140 @Require @Param message: string; 141 @Require @Param flag: boolean; 142 build() { 143 Column() { 144 Text(`Param ${this.count}`) 145 Text(`Param ${this.message}`) 146 Text(`Param ${this.flag}`) 147 } 148 } 149 } 150 ``` 151 152- When the decorated variable is of a class object type, only the overall value changes to the class object can be observed. To observe value changes to the member properties in the class object, you'll need the \@ObservedV2 and \@Trace decorators. 153 154 ```ts 155 class RawObject { 156 name: string; 157 constructor(name: string) { 158 this.name = name; 159 } 160 } 161 @ObservedV2 162 class ObservedObject { 163 @Trace name: string; 164 constructor(name: string) { 165 this.name = name; 166 } 167 } 168 @Entry 169 @ComponentV2 170 struct Index { 171 @Local rawObject: RawObject = new RawObject("rawObject"); 172 @Local observedObject: ObservedObject = new ObservedObject("observedObject"); 173 build() { 174 Column() { 175 Text(`${this.rawObject.name}`) 176 Text(`${this.observedObject.name}`) 177 Button("change object") 178 .onClick(() => { 179 // Overall changes to the class object can be observed. 180 this.rawObject = new RawObject("new rawObject"); 181 this.observedObject = new ObservedObject("new observedObject"); 182 }) 183 Button("change name") 184 .onClick(() => { 185 // \@Local and \@Param cannot observe the class object properties. Therefore, the changes of rawObject.name cannot be observed. 186 this.rawObject.name = "new rawObject name"; 187 // The name property of ObservedObject is decorated by @Trace. Therefore, the changes of observedObject.name can be observed. 188 this.observedObject.name = "new observedObject name"; 189 }) 190 Child({ 191 rawObject: this.rawObject, 192 observedObject: this.observedObject 193 }) 194 } 195 } 196 } 197 @ComponentV2 198 struct Child { 199 @Require @Param rawObject: RawObject; 200 @Require @Param observedObject: ObservedObject; 201 build() { 202 Column() { 203 Text(`${this.rawObject.name}`) 204 Text(`${this.observedObject.name}`) 205 } 206 } 207 208 } 209 ``` 210 211- When the decorated variable type is a simple array, you can observe the changes of the entire array or the array items. 212 213 ```ts 214 @Entry 215 @ComponentV2 216 struct Index { 217 @Local numArr: number[] = [1,2,3,4,5]; 218 @Local dimensionTwo: number[][] = [[1,2,3],[4,5,6]]; 219 220 build() { 221 Column() { 222 Text(`${this.numArr[0]}`) 223 Text(`${this.numArr[1]}`) 224 Text(`${this.numArr[2]}`) 225 Text(`${this.dimensionTwo[0][0]}`) 226 Text(`${this.dimensionTwo[1][1]}`) 227 Button("change array item") 228 .onClick(() => { 229 this.numArr[0]++; 230 this.numArr[1] += 2; 231 this.dimensionTwo[0][0] = 0; 232 this.dimensionTwo[1][1] = 0; 233 }) 234 Button("change whole array") 235 .onClick(() => { 236 this.numArr = [5,4,3,2,1]; 237 this.dimensionTwo = [[7,8,9],[0,1,2]]; 238 }) 239 Child({ 240 numArr: this.numArr, 241 dimensionTwo: this.dimensionTwo 242 }) 243 } 244 } 245 } 246 @ComponentV2 247 struct Child { 248 @Require @Param numArr: number[]; 249 @Require @Param dimensionTwo: number[][]; 250 251 build() { 252 Column() { 253 Text(`${this.numArr[0]}`) 254 Text(`${this.numArr[1]}`) 255 Text(`${this.numArr[2]}`) 256 Text(`${this.dimensionTwo[0][0]}`) 257 Text(`${this.dimensionTwo[1][1]}`) 258 } 259 } 260 } 261 ``` 262 263- When the decorated variable is of a nested class or is an object array, \@Param cannot observe the change of lower-level object attributes. Observation of lower-level object attributes requires the use of \@ObservedV2 and \@Trace decorators. 264 265 ```ts 266 @ObservedV2 267 class Region { 268 @Trace x: number; 269 @Trace y: number; 270 constructor(x: number, y: number) { 271 this.x = x; 272 this.y = y; 273 } 274 } 275 @ObservedV2 276 class Info { 277 @Trace region: Region; 278 @Trace name: string; 279 constructor(name: string, x: number, y: number) { 280 this.name = name; 281 this.region = new Region(x, y); 282 } 283 } 284 @Entry 285 @ComponentV2 286 struct Index { 287 @Local infoArr: Info[] = [new Info("Ocean", 28, 120), new Info("Mountain", 26, 20)]; 288 @Local originInfo: Info = new Info("Origin", 0, 0); 289 build() { 290 Column() { 291 ForEach(this.infoArr, (info: Info) => { 292 Row() { 293 Text(`name: ${info.name}`) 294 Text(`region: ${info.region.x}-${info.region.y}`) 295 } 296 }) 297 Row() { 298 Text(`Origin name: ${this.originInfo.name}`) 299 Text(`Origin region: ${this.originInfo.region.x}-${this.originInfo.region.y}`) 300 } 301 Button("change infoArr item") 302 .onClick(() => { 303 // Because the name property is decorated by @Trace, it can be observed. 304 this.infoArr[0].name = "Win"; 305 }) 306 Button("change originInfo") 307 .onClick(() => { 308 // Because the variable originInfo is decorated by @Local, it can be observed. 309 this.originInfo = new Info("Origin", 100, 100); 310 }) 311 Button("change originInfo region") 312 .onClick(() => { 313 // Because the x and y properties are decorated by @Trace, it can be observed. 314 this.originInfo.region.x = 25; 315 this.originInfo.region.y = 25; 316 }) 317 } 318 } 319 } 320 @ComponentV2 321 struct Child { 322 @Param infoArr: Info[] = []; 323 @Param originInfo: Info = new Info("O", 0, 0); 324 325 build() { 326 Column() { 327 ForEach(this.infoArr, (info: Info) => { 328 Row() { 329 Text(`name: ${info.name}`) 330 Text(`region: ${info.region.x}-${info.region.y}`) 331 } 332 }) 333 Row() { 334 Text(`Origin name: ${this.originInfo.name}`) 335 Text(`Origin region: ${this.originInfo.region.x}-${this.originInfo.region.y}`) 336 } 337 } 338 } 339 } 340 ``` 341 342- When the decorated variable is of a built-in type, you can observe the overall value assignment of the variable and the changes caused by API invoking. 343 344 | Type | Observable APIs | 345 | ----- | ------------------------------------------------------------ | 346 | Array | push, pop, shift, unshift, splice, copyWithin, fill, reverse, sort| 347 | Date | setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds | 348 | Map | set, clear, delete | 349 | Set | add, clear, delete | 350 351## Constraints 352 353The \@Param decorator has the following constraints: 354 355- The \@Param decorator can be used only in custom components decorated by \@ComponentV2. 356 357 ```ts 358 @ComponentV2 359 struct CompA { 360 @Param message: string = "Hello World"; // Correct usage. 361 build() { 362 } 363 } 364 @Component 365 struct CompB { 366 @Param message: string = "Hello World"; // Incorrect usage. 367 build() { 368 } 369 } 370 ``` 371 372- The \@Param decorated variable indicates the external input of the component and needs to be initialized. The local initial value can be used for initialization. But if the external input value exists, it is preferentially used for initialization. It is not allowed to use neither the local initial value nor the external input value. 373 374 ```ts 375 @ComponentV2 376 struct CompA { 377 @Param param1: string = "Initialize local"; 378 @Param param2: string = "Initialize local and put in"; 379 @Require @Param param3: string; 380 build() { 381 Column() { 382 Text(`${this.param1}`) // Initialize local 383 Text(`${this.param2}`) // Put in 384 Text(`${this.param3}`) // Put in 385 } 386 } 387 } 388 @Entry 389 @ComponentV2 390 struct CompB { 391 @Local message: string = "Put in"; 392 build() { 393 Column() { 394 CompA({ 395 param2: this.message, 396 param3: this.message 397 }) 398 } 399 } 400 } 401 ``` 402 403- \@Param decorated variables cannot be changed in the child component. However, when the decorated variable is of object type, changing the object properties in the child component is allowed. 404 405 ```ts 406 @ObservedV2 407 class Info { 408 @Trace name: string; 409 constructor(name: string) { 410 this.name = name; 411 } 412 } 413 @Entry 414 @ComponentV2 415 struct Index { 416 @Local info: Info = new Info("Tom"); 417 build() { 418 Column() { 419 Text(`Parent info.name ${this.info.name}`) 420 Button("Parent change info") 421 .onClick(() => { 422 this.info = new Info("Lucy"); // When the @Local variable is changed in the parent component, the @Param variable corresponding to the child component is synchronized. 423 }) 424 Child({ info: this.info }) 425 } 426 } 427 } 428 @ComponentV2 429 struct Child { 430 @Require @Param info: Info; 431 build() { 432 Column() { 433 Text(`info.name: ${this.info.name}`) 434 Button("change info") 435 .onClick(() => { 436 this.info = new Info("Jack"); // Incorrect usage. The @Param variables cannot be changed in the child component. 437 }) 438 Button("Child change info.name") 439 .onClick(() => { 440 this.info.name = "Jack"; // Changing the object properties in the child component is allowed. 441 }) 442 } 443 } 444 } 445 ``` 446 447## Use Scenarios 448 449### Passing and Synchronizing Variables from the Parent Component to the Child Component 450 451\@Param receives and synchronizes the data passed in by the \@Local or \@Param parent component in real time. 452 453```ts 454@ObservedV2 455class Region { 456 @Trace x: number; 457 @Trace y: number; 458 constructor(x: number, y: number) { 459 this.x = x; 460 this.y = y; 461 } 462} 463@ObservedV2 464class Info { 465 @Trace name: string; 466 @Trace age: number; 467 @Trace region: Region; 468 constructor(name: string, age: number, x: number, y: number) { 469 this.name = name; 470 this.age = age; 471 this.region = new Region(x, y); 472 } 473} 474@Entry 475@ComponentV2 476struct Index { 477 @Local infoList: Info[] = [new Info("Alice", 8, 0, 0), new Info("Barry", 10, 1, 20), new Info("Cindy", 18, 24, 40)]; 478 build() { 479 Column() { 480 ForEach(this.infoList, (info: Info) => { 481 MiddleComponent({ info: info }) 482 }) 483 Button("change") 484 .onClick(() => { 485 this.infoList[0] = new Info("Atom", 40, 27, 90); 486 this.infoList[1].name = "Bob"; 487 this.infoList[2].region = new Region(7, 9); 488 }) 489 } 490 } 491} 492@ComponentV2 493struct MiddleComponent { 494 @Require @Param info: Info; 495 build() { 496 Column() { 497 Text(`name: ${this.info.name}`) 498 Text(`age: ${this.info.age}`) 499 SubComponent({ region: this.info.region }) 500 } 501 } 502} 503@ComponentV2 504struct SubComponent { 505 @Require @Param region: Region; 506 build() { 507 Column() { 508 Text(`region: ${this.region.x}-${this.region.y}`) 509 } 510 } 511} 512``` 513 514### Decorating Variables of the Date Type 515 516By using \@Param to decorate the variables of the Date type, you can observe the value changes to the entire **Date** and the changes brought by calling the **Date** APIs: **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, and **setUTCMilliseconds**. 517 518```ts 519@ComponentV2 520struct DateComponent { 521 @Param selectedDate: Date = new Date('2024-01-01'); 522 523 build() { 524 Column() { 525 DatePicker({ 526 start: new Date('1970-1-1'), 527 end: new Date('2100-1-1'), 528 selected: this.selectedDate 529 }) 530 } 531 } 532} 533 534@Entry 535@ComponentV2 536struct ParentComponent { 537 @Local parentSelectedDate: Date = new Date('2021-08-08'); 538 539 build() { 540 Column() { 541 Button('parent update the new date') 542 .margin(10) 543 .onClick(() => { 544 this.parentSelectedDate = new Date('2023-07-07') 545 }) 546 Button('increase the year by 1') 547 .margin(10) 548 .onClick(() => { 549 this.parentSelectedDate.setFullYear(this.parentSelectedDate.getFullYear() + 1) 550 }) 551 Button('increase the month by 1') 552 .margin(10) 553 .onClick(() => { 554 this.parentSelectedDate.setMonth(this.parentSelectedDate.getMonth() + 1) 555 }) 556 Button('parent increase the day by 1') 557 .margin(10) 558 .onClick(() => { 559 this.parentSelectedDate.setDate(this.parentSelectedDate.getDate() + 1) 560 }) 561 DateComponent({ selectedDate: this.parentSelectedDate }) 562 } 563 } 564} 565``` 566 567### Decorating Variables of the Map Type 568 569By using \@Param to decorate the variables of the **Map** type, you can observe the overall value changes to the entire **Map** and the changes brought by calling the **Map** APIs: set, clear, and delete. 570 571```ts 572@ComponentV2 573struct Child { 574 @Param value: Map<number, string> = new Map() 575 576 build() { 577 Column() { 578 ForEach(Array.from(this.value.entries()), (item: [number, string]) => { 579 Text(`${item[0]}`).fontSize(30) 580 Text(`${item[1]}`).fontSize(30) 581 Divider() 582 }) 583 } 584 } 585} 586@Entry 587@ComponentV2 588struct MapSample2 { 589 @Local message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]) 590 591 build() { 592 Row() { 593 Column() { 594 Child({ value: this.message }) 595 Button('init map').onClick(() => { 596 this.message = new Map([[0, "a"], [1, "b"], [3, "c"]]) 597 }) 598 Button('set new one').onClick(() => { 599 this.message.set(4, "d") 600 }) 601 Button('clear').onClick(() => { 602 this.message.clear() 603 }) 604 Button('replace the first one').onClick(() => { 605 this.message.set(0, "aa") 606 }) 607 Button('delete the first one').onClick(() => { 608 this.message.delete(0) 609 }) 610 } 611 .width('100%') 612 } 613 .height('100%') 614 } 615} 616``` 617 618### Decorating Variables of the Set Type 619 620By using \@Param to decorate the variables of the **Set** type, you can observe the overall value changes to the entire **Set** and the changes brought by calling the **Set** APIs: add, clear, and delete. 621 622```ts 623@ComponentV2 624struct Child { 625 @Param message: Set<number> = new Set() 626 627 build() { 628 Column() { 629 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 630 Text(`${item[0]}`).fontSize(30) 631 Divider() 632 }) 633 } 634 .width('100%') 635 } 636} 637@Entry 638@ComponentV2 639struct SetSample11 { 640 @Local message: Set<number> = new Set([0, 1, 2, 3, 4]) 641 642 build() { 643 Row() { 644 Column() { 645 Child({ message: this.message }) 646 Button('init set').onClick(() => { 647 this.message = new Set([0, 1, 2, 3, 4]) 648 }) 649 Button('set new one').onClick(() => { 650 this.message.add(5) 651 }) 652 Button('clear').onClick(() => { 653 this.message.clear() 654 }) 655 Button('delete the first one').onClick(() => { 656 this.message.delete(0) 657 }) 658 } 659 .width('100%') 660 } 661 .height('100%') 662 } 663} 664``` 665 666### Union Type 667 668\@Param supports null, undefined, and union types. In the following example, the **count** type is of **number | undefined**. Click to change the **count** type, the UI will be re-rendered accordingly. 669 670```ts 671@Entry 672@ComponentV2 673struct Index { 674 @Local count: number | undefined = 0; 675 676 build() { 677 Column() { 678 MyComponent({ count: this.count }) 679 Button('change') 680 .onClick(() => { 681 this.count = undefined; 682 }) 683 } 684 } 685} 686 687@ComponentV2 688struct MyComponent { 689 @Param count: number | undefined = 0; 690 691 build() { 692 Column() { 693 Text(`count(${this.count})`) 694 } 695 } 696} 697``` 698