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