• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Param:组件外部输入
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @jiyujia926-->
5<!--Designer: @s10021109-->
6<!--Tester: @TerryTsao-->
7<!--Adviser: @zhang_yixin13-->
8
9
10为了增强子组件接受外部参数输入的能力,开发者可以使用\@Param装饰器。
11
12
13\@Param不仅可以接受组件外部输入,还可以接受\@Local的同步变化。在阅读本文档前,建议提前阅读:[\@Local](./arkts-new-local.md)。
14
15> **说明:**
16>
17> 从API version 12开始,在\@ComponentV2装饰的自定义组件中支持使用\@Param装饰器。
18>
19> 从API version 12开始,该装饰器支持在原子化服务中使用。
20
21## 概述
22
23\@Param表示组件从外部传入的状态,使得父子组件之间的数据能够进行同步:
24
25- \@Param装饰的变量支持本地初始化,但不允许在组件内部直接修改。
26
27- 被\@Param装饰的变量能够在初始化自定义组件时从外部传入,当数据源也是状态变量时,数据源的修改会同步给\@Param。
28- \@Param可以接受任意类型的数据源,包括普通变量、状态变量、常量、函数返回值等。
29- \@Param装饰的变量变化时,会刷新该变量关联的组件。
30- \@Param支持对基本类型(如number、boolean、string、Object、class)、内嵌类型(如[Array](#装饰array类型变量)、[Set](#装饰set类型变量)、[Map](#装饰map类型变量)、[Date](#装饰date类型变量)),以及null、undefined和[联合类型](#联合类型)进行观测。
31- 对于复杂类型如类对象,\@Param会接受数据源的引用。在组件内可以修改类对象中的属性,该修改会同步到数据源。
32- \@Param的观测能力仅限于被装饰的变量本身。详见[观察变化](#观察变化)。
33
34
35## 状态管理V1版本接受外部传入的装饰器的局限性
36状态管理V1存在多种可接受外部传入的装饰器,常用的有[\@State](arkts-state.md)、[\@Prop](arkts-prop.md)、[\@Link](arkts-link.md)、[\@ObjectLink](arkts-observed-and-objectlink.md)。这些装饰器使用有限制且不易区分,不当使用会导致性能问题。
37
38```ts
39@Observed
40class Region {
41  x: number;
42  y: number;
43  constructor(x: number, y: number) {
44    this.x = x;
45    this.y = y;
46  }
47}
48@Observed
49class Info {
50  region: Region;
51  constructor(x: number, y: number) {
52    this.region = new Region(x, y);
53  }
54}
55@Entry
56@Component
57struct Index {
58  @State info: Info = new Info(0, 0);
59
60  build() {
61    Column() {
62      Button('change Info')
63        .onClick(() => {
64          this.info = new Info(100, 100);
65      })
66      Child({
67        region: this.info.region,
68        regionProp: this.info.region,
69        infoProp: this.info,
70        infoLink: this.info,
71        infoState: this.info
72      })
73    }
74  }
75}
76@Component
77struct Child {
78  @ObjectLink region: Region;
79  @Prop regionProp: Region;
80  @Prop infoProp: Info;
81  @Link infoLink: Info;
82  @State infoState: Info = new Info(1, 1);
83  build() {
84    Column() {
85      Text(`ObjectLink region: ${this.region.x}-${this.region.y}`)
86      Text(`Prop regionProp: ${this.regionProp.x}-${this.regionProp.y}`)
87    }
88  }
89}
90```
91
92在上面的示例中,\@State仅能在初始化时接收info的引用,改变info之后无法同步。\@Prop虽然能够进行单向同步,但是对于较复杂的类型来说,深拷贝性能较差。\@Link能够接受传入的引用进行双向同步,但它必须要求数据源也是状态变量,因此无法接受info中的成员属性region。\@ObjectLink能够接受类成员属性,但是要求该属性类型必须为\@Observed装饰的类。装饰器的不同限制使得父子组件之间的传值规则复杂、不易使用。因此推出\@Param装饰器,表示组件从外部传入的状态。
93
94## 装饰器说明
95
96| \@Param变量装饰器  | 说明                                                         |
97| ------------------ | ------------------------------------------------------------ |
98| 装饰器参数         | 无。                                                         |
99| 能否本地修改       | 否。若需要修改值,可使用\@Param搭配[\@Once](./arkts-new-once.md)修改子组件的本地值。或通过[\@Event](./arkts-new-event.md)装饰器,修改\@Param数据源的值。|
100| 同步类型           | 由父到子单向同步。                                           |
101| 允许装饰的变量类型 | Object、class、string、number、boolean、enum等基本类型以及Array、Date、Map、Set等内嵌类型。支持null、undefined以及联合类型。 |
102| 被装饰变量的初始值 | 允许本地初始化,若不在本地初始化,则需要和[\@Require](./arkts-require.md)装饰器一起使用,要求必须从外部传入初始化。 |
103
104## 变量传递
105
106| 传递规则       | 说明                                                         |
107| -------------- | ------------------------------------------------------------ |
108| 从父组件初始化 | \@Param装饰的变量允许本地初始化,若无本地初始化则必须从外部传入初始化。当同时存在本地初始值与外部传入值时,优先使用外部传入值进行初始化。 |
109| 初始化子组件   | \@Param装饰的变量可以初始化子组件中\@Param装饰的变量。       |
110| 同步           | \@Param可以和父组件传入的状态变量数据源(即\@Local或\@Param装饰的变量)进行同步,当数据源发生变化时,会将修改同步给子组件的\@Param。 |
111
112## 观察变化
113
114使用\@Param装饰的变量具有被观测变化的能力。当装饰的变量发生变化时,会触发该变量绑定的UI组件刷新。
115
116- 当装饰的变量类型为boolean、string、number类型时,可观察数据源同步变化。
117
118  ```ts
119  @Entry
120  @ComponentV2
121  struct Index {
122    @Local count: number = 0;
123    @Local message: string = 'Hello';
124    @Local flag: boolean = false;
125    build() {
126      Column() {
127        Text(`Local ${this.count}`)
128        Text(`Local ${this.message}`)
129        Text(`Local ${this.flag}`)
130        Button('change Local')
131          .onClick(()=>{
132            // 对数据源的更改会同步给子组件
133            this.count++;
134            this.message += ' World';
135            this.flag = !this.flag;
136        })
137        Child({
138          count: this.count,
139          message: this.message,
140          flag: this.flag
141        })
142      }
143    }
144  }
145  @ComponentV2
146  struct Child {
147    @Require @Param count: number;
148    @Require @Param message: string;
149    @Require @Param flag: boolean;
150    build() {
151      Column() {
152        Text(`Param ${this.count}`)
153        Text(`Param ${this.message}`)
154        Text(`Param ${this.flag}`)
155      }
156    }
157  }
158  ```
159
160- 当装饰的变量类型为类对象时,仅可以观察到对类对象整体赋值的变化,无法直接观察到对类成员属性赋值的变化,对类成员属性的观察依赖[\@ObservedV2](arkts-new-observedV2-and-trace.md)和[\@Trace](arkts-new-observedV2-and-trace.md)装饰器。
161
162  ```ts
163  class RawObject {
164    name: string;
165    constructor(name: string) {
166      this.name = name;
167    }
168  }
169  @ObservedV2
170  class ObservedObject {
171    @Trace name: string;
172    constructor(name: string) {
173      this.name = name;
174    }
175  }
176  @Entry
177  @ComponentV2
178  struct Index {
179    @Local rawObject: RawObject = new RawObject('rawObject');
180    @Local observedObject: ObservedObject = new ObservedObject('observedObject');
181    build() {
182      Column() {
183        Text(`${this.rawObject.name}`)
184        Text(`${this.observedObject.name}`)
185        Button('change object')
186          .onClick(() => {
187            // 对类对象整体的修改均能观察到
188            this.rawObject = new RawObject('new rawObject');
189            this.observedObject = new ObservedObject('new observedObject');
190        })
191        Button('change name')
192          .onClick(() => {
193            // @Local与@Param均不具备观察类对象属性的能力,因此对rawObject.name的修改无法观察到
194            this.rawObject.name = 'new rawObject name';
195            // 由于ObservedObject的name属性被@Trace装饰,因此对observedObject.name的修改能被观察到
196            this.observedObject.name = 'new observedObject name';
197        })
198        Child({
199          rawObject: this.rawObject,
200          observedObject: this.observedObject
201        })
202      }
203    }
204  }
205  @ComponentV2
206  struct Child {
207    @Require @Param rawObject: RawObject;
208    @Require @Param observedObject: ObservedObject;
209    build() {
210      Column() {
211        Text(`${this.rawObject.name}`)
212        Text(`${this.observedObject.name}`)
213      }
214    }
215  }
216  ```
217
218- 装饰的变量为简单类型数组时,可观察数组整体或数组项变化。
219
220  ```ts
221  @Entry
222  @ComponentV2
223  struct Index {
224    @Local numArr: number[] = [1,2,3,4,5];
225    @Local dimensionTwo: number[][] = [[1,2,3],[4,5,6]];
226
227    build() {
228      Column() {
229        Text(`${this.numArr[0]}`)
230        Text(`${this.numArr[1]}`)
231        Text(`${this.numArr[2]}`)
232        Text(`${this.dimensionTwo[0][0]}`)
233        Text(`${this.dimensionTwo[1][1]}`)
234        Button('change array item')
235          .onClick(() => {
236            this.numArr[0]++;
237            this.numArr[1] += 2;
238            this.dimensionTwo[0][0] = 0;
239            this.dimensionTwo[1][1] = 0;
240          })
241        Button('change whole array')
242          .onClick(() => {
243            this.numArr = [5,4,3,2,1];
244            this.dimensionTwo = [[7,8,9],[0,1,2]];
245          })
246        Child({
247          numArr: this.numArr,
248          dimensionTwo: this.dimensionTwo
249        })
250      }
251    }
252  }
253  @ComponentV2
254  struct Child {
255    @Require @Param numArr: number[];
256    @Require @Param dimensionTwo: number[][];
257
258    build() {
259      Column() {
260        Text(`${this.numArr[0]}`)
261        Text(`${this.numArr[1]}`)
262        Text(`${this.numArr[2]}`)
263        Text(`${this.dimensionTwo[0][0]}`)
264        Text(`${this.dimensionTwo[1][1]}`)
265      }
266    }
267  }
268  ```
269
270- 当装饰的变量是嵌套类或对象数组时,\@Param无法观察深层对象属性的变化。对深层对象属性的观测依赖\@ObservedV2与\@Trace装饰器。
271
272  ```ts
273  @ObservedV2
274  class Region {
275    @Trace x: number;
276    @Trace y: number;
277    constructor(x: number, y: number) {
278      this.x = x;
279      this.y = y;
280    }
281  }
282  @ObservedV2
283  class Info {
284    @Trace region: Region;
285    @Trace name: string;
286    constructor(name: string, x: number, y: number) {
287      this.name = name;
288      this.region = new Region(x, y);
289    }
290  }
291  @Entry
292  @ComponentV2
293  struct Index {
294    @Local infoArr: Info[] = [new Info('Ocean', 28, 120), new Info('Mountain', 26, 20)];
295    @Local originInfo: Info = new Info('Origin', 0, 0);
296    build() {
297      Column() {
298        ForEach(this.infoArr, (info: Info) => {
299          Row() {
300            Text(`name: ${info.name}`)
301            Text(`region: ${info.region.x}-${info.region.y}`)
302          }
303        })
304        Row() {
305          Text(`Origin name: ${this.originInfo.name}`)
306          Text(`Origin region: ${this.originInfo.region.x}-${this.originInfo.region.y}`)
307        }
308        Button('change infoArr item')
309          .onClick(() => {
310            // 由于属性name被@Trace装饰,所以能够观察到
311            this.infoArr[0].name = 'Win';
312          })
313        Button('change originInfo')
314          .onClick(() => {
315            // 由于变量originInfo被@Local装饰,所以能够观察到
316            this.originInfo = new Info('Origin', 100, 100);
317          })
318        Button('change originInfo region')
319          .onClick(() => {
320            // 由于属性x、y被@Trace装饰,所以能够观察到
321            this.originInfo.region.x = 25;
322            this.originInfo.region.y = 25;
323          })
324        Child({
325          infoArr: this.infoArr,
326          originInfo: this.originInfo
327        })
328      }
329    }
330  }
331  @ComponentV2
332  struct Child {
333    @Param infoArr: Info[] = [];
334    @Param originInfo: Info = new Info('O', 0, 0);
335
336    build() {
337      Column() {
338        ForEach(this.infoArr, (info: Info) => {
339          Row() {
340            Text(`name: ${info.name}`)
341            Text(`region: ${info.region.x}-${info.region.y}`)
342          }
343        })
344        Row() {
345          Text(`Origin name: ${this.originInfo.name}`)
346          Text(`Origin region: ${this.originInfo.region.x}-${this.originInfo.region.y}`)
347        }
348      }
349    }
350  }
351  ```
352
353- 装饰的变量为内置类型时,可观察变量整体赋值和API调用的变化。
354
355  | 类型  | 可观测变化的API                                              |
356  | ----- | ------------------------------------------------------------ |
357  | Array | push, pop, shift, unshift, splice, copyWithin, fill, reverse, sort |
358  | Date  | setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds |
359  | Map   | set, clear, delete                                           |
360  | Set   | add, clear, delete                                           |
361
362## 限制条件
363
364\@Param装饰器存在以下使用限制:
365
366- \@Param装饰器只能在[\@ComponentV2](arkts-new-componentV2.md)装饰器的自定义组件中使用。
367
368  ```ts
369  @ComponentV2
370  struct MyComponent {
371    @Param message: string = 'Hello World'; // 正确用法
372    build() {
373    }
374  }
375  @Component
376  struct TestComponent {
377    @Param message: string = 'Hello World'; // 错误用法,编译时报错
378    build() {
379    }
380  }
381  ```
382
383- \@Param装饰的变量表示组件外部输入,需要初始化。支持使用本地初始值或外部传入值进行初始化。当存在外部传入值时,优先使用外部传入值。不允许既不使用本地初始值,也不使用外部传入值。
384
385  ```ts
386  @ComponentV2
387  struct ChildComponent {
388    @Param param1: string = 'Initialize local';
389    @Param param2: string = 'Initialize local and put in';
390    @Require @Param param3: string;
391    @Param param4: string; // 错误用法,外部未传入初始化且本地也无初始值,编译报错
392    build() {
393      Column() {
394        Text(`${this.param1}`) // 本地初始化,显示Initialize local
395        Text(`${this.param2}`) // 外部传入初始化,显示Put in
396        Text(`${this.param3}`) // 外部传入初始化,显示Put in
397      }
398    }
399  }
400  @Entry
401  @ComponentV2
402  struct MyComponent {
403    @Local message: string = 'Put in';
404    build() {
405      Column() {
406        ChildComponent({
407          param2: this.message,
408          param3: this.message
409        })
410      }
411    }
412  }
413  ```
414
415- 使用`@Param`装饰的变量在子组件中无法被直接修改。但是,如果装饰的变量是对象类型,在子组件中可以修改对象的属性。
416
417  ```ts
418  @ObservedV2
419  class Info {
420    @Trace name: string;
421    constructor(name: string) {
422      this.name = name;
423    }
424  }
425  @Entry
426  @ComponentV2
427  struct Index {
428    @Local info: Info = new Info('Tom');
429    build() {
430      Column() {
431        Text(`Parent info.name ${this.info.name}`)
432        Button('Parent change info')
433          .onClick(() => {
434            // 父组件更改@Local变量,会同步子组件对应@Param变量
435            this.info = new Info('Lucy');
436        })
437        Child({ info: this.info })
438      }
439    }
440  }
441  @ComponentV2
442  struct Child {
443    @Require @Param info: Info;
444    build() {
445      Column() {
446        Text(`info.name: ${this.info.name}`)
447        Button('change info')
448          .onClick(() => {
449            // 错误用法,不允许在子组件中更改@Param变量,编译时会报错
450            this.info = new Info('Jack');
451          })
452        Button('Child change info.name')
453          .onClick(() => {
454            // 允许在子组件中更改对象中属性,该修改会同步到父组件数据源上,当属性被@Trace装饰时,可观测到对应UI刷新
455            this.info.name = 'Jack';
456          })
457      }
458    }
459  }
460  ```
461
462## 使用场景
463
464### 从父组件到子组件变量传递与同步
465
466\@Param能够接受父组件\@Local或\@Param传递的数据并与之变化同步。
467
468```ts
469@ObservedV2
470class Region {
471  @Trace x: number;
472  @Trace y: number;
473  constructor(x: number, y: number) {
474    this.x = x;
475    this.y = y;
476  }
477}
478@ObservedV2
479class Info {
480  @Trace name: string;
481  @Trace age: number;
482  @Trace region: Region;
483  constructor(name: string, age: number, x: number, y: number) {
484    this.name = name;
485    this.age = age;
486    this.region = new Region(x, y);
487  }
488}
489@Entry
490@ComponentV2
491struct Index {
492  @Local infoList: Info[] = [new Info('Alice', 8, 0, 0), new Info('Barry', 10, 1, 20), new Info('Cindy', 18, 24, 40)];
493  build() {
494    Column() {
495      ForEach(this.infoList, (info: Info) => {
496        MiddleComponent({ info: info })
497      })
498      Button('change')
499        .onClick(() => {
500          this.infoList[0] = new Info('Atom', 40, 27, 90);
501          this.infoList[1].name = 'Bob';
502          this.infoList[2].region = new Region(7, 9);
503        })
504    }
505  }
506}
507@ComponentV2
508struct MiddleComponent {
509  @Require @Param info: Info;
510  build() {
511    Column() {
512      Text(`name: ${this.info.name}`)
513      Text(`age: ${this.info.age}`)
514      SubComponent({ region: this.info.region })
515    }
516  }
517}
518@ComponentV2
519struct SubComponent {
520  @Require @Param region: Region;
521  build() {
522    Column() {
523      Text(`region: ${this.region.x}-${this.region.y}`)
524    }
525  }
526}
527```
528
529### 装饰Array类型变量
530\@Param装饰Array类型变量,可以观察到数据源对Array整体的赋值,以及调用Array的接口`push`, `pop`, `shift`, `unshift`, `splice`, `copyWithin`, `fill`, `reverse`, `sort`带来的变化。
531
532```ts
533@ComponentV2
534struct Child {
535  @Require @Param count: number[];
536
537  build() {
538    Column() {
539      ForEach(this.count, (item: number) => {
540        Text(`${item}`).fontSize(30)
541        Divider()
542      })
543    }
544    .width('100%')
545  }
546}
547@Entry
548@ComponentV2
549struct Index {
550  @Local count: number[] = [1,2,3];
551
552  build() {
553    Row() {
554      Column() {
555        Child({ count: this.count })
556        Button('init array').onClick(() => {
557          this.count = [9,8,7];
558        })
559        Button('push').onClick(() => {
560          this.count.push(0);
561        })
562        Button('reverse').onClick(() => {
563          this.count.reverse();
564        })
565        Button('fill').onClick(() => {
566          this.count.fill(6);
567        })
568      }
569      .width('100%')
570    }
571    .height('100%')
572  }
573}
574```
575
576
577
578### 装饰Date类型变量
579
580\@Param装饰Date类型变量,可以观察到数据源对Date整体的赋值,以及调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds`带来的变化。
581
582```ts
583@ComponentV2
584struct DateComponent {
585  @Param selectedDate: Date = new Date('2024-01-01');
586
587  build() {
588    Column() {
589      DatePicker({
590        start: new Date('1970-1-1'),
591        end: new Date('2100-1-1'),
592        selected: this.selectedDate
593      })
594    }
595  }
596}
597
598@Entry
599@ComponentV2
600struct Index {
601  @Local parentSelectedDate: Date = new Date('2021-08-08');
602
603  build() {
604    Column() {
605      Button('parent update the new date')
606        .margin(10)
607        .onClick(() => {
608          this.parentSelectedDate = new Date('2023-07-07');
609        })
610      Button('increase the year by 1')
611        .margin(10)
612        .onClick(() => {
613          this.parentSelectedDate.setFullYear(this.parentSelectedDate.getFullYear() + 1);
614        })
615      Button('increase the month by 1')
616        .margin(10)
617        .onClick(() => {
618          this.parentSelectedDate.setMonth(this.parentSelectedDate.getMonth() + 1);
619        })
620      Button('parent increase the day by 1')
621        .margin(10)
622        .onClick(() => {
623          this.parentSelectedDate.setDate(this.parentSelectedDate.getDate() + 1);
624        })
625      DateComponent({ selectedDate: this.parentSelectedDate })
626    }
627  }
628}
629```
630
631### 装饰Map类型变量
632
633\@Param装饰Map类型变量,可以观察到数据源对Map整体的赋值,以及调用Map的接口`set`, `clear`, `delete`带来的变化。
634
635```ts
636@ComponentV2
637struct Child {
638  @Param value: Map<number, string> = new Map();
639
640  build() {
641    Column() {
642      ForEach(Array.from(this.value.entries()), (item: [number, string]) => {
643        Text(`${item[0]}`).fontSize(30)
644        Text(`${item[1]}`).fontSize(30)
645        Divider()
646      })
647    }
648  }
649}
650@Entry
651@ComponentV2
652struct Index {
653  @Local message: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
654
655  build() {
656    Row() {
657      Column() {
658        Child({ value: this.message })
659        Button('init map').onClick(() => {
660          this.message = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
661        })
662        Button('set new one').onClick(() => {
663          this.message.set(4, 'd');
664        })
665        Button('clear').onClick(() => {
666          this.message.clear();
667        })
668        Button('replace the first one').onClick(() => {
669          this.message.set(0, 'aa');
670        })
671        Button('delete the first one').onClick(() => {
672          this.message.delete(0);
673        })
674      }
675      .width('100%')
676    }
677    .height('100%')
678  }
679}
680```
681
682### 装饰Set类型变量
683
684\@Param装饰Set类型变量,可以观察到数据源对Set整体的赋值,以及调用Set的接口`add`, `clear`, `delete`带来的变化。
685
686```ts
687@ComponentV2
688struct Child {
689  @Param message: Set<number> = new Set();
690
691  build() {
692    Column() {
693      ForEach(Array.from(this.message.entries()), (item: [number, number]) => {
694        Text(`${item[0]}`).fontSize(30)
695        Divider()
696      })
697    }
698    .width('100%')
699  }
700}
701@Entry
702@ComponentV2
703struct Index {
704  @Local message: Set<number> = new Set([0, 1, 2, 3, 4]);
705
706  build() {
707    Row() {
708      Column() {
709        Child({ message: this.message })
710        Button('init set').onClick(() => {
711          this.message = new Set([0, 1, 2, 3, 4]);
712        })
713        Button('set new one').onClick(() => {
714          this.message.add(5);
715        })
716        Button('clear').onClick(() => {
717          this.message.clear();
718        })
719        Button('delete the first one').onClick(() => {
720          this.message.delete(0);
721        })
722      }
723      .width('100%')
724    }
725    .height('100%')
726  }
727}
728```
729
730### 联合类型
731
732\@Param支持null、undefined以及联合类型。以下示例中,count类型为number | undefined,点击改变count的类型时,UI会自动刷新。
733
734```ts
735@Entry
736@ComponentV2
737struct Index {
738  @Local count: number | undefined = 0;
739
740  build() {
741    Column() {
742      MyComponent({ count: this.count })
743      Button('change')
744        .onClick(() => {
745          this.count = undefined;
746        })
747    }
748  }
749}
750
751@ComponentV2
752struct MyComponent {
753  @Param count: number | undefined = 0;
754
755  build() {
756    Column() {
757      Text(`count(${this.count})`)
758    }
759  }
760}
761```