• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Param:组件外部输入
2
3
4为了增强子组件接受外部参数输入的能力,开发者可以使用\@Param装饰器。
5
6
7\@Param不仅可以接受组件外部输入,还可以接受\@Local的同步变化。在阅读本文档前,建议提前阅读:[\@Local](./arkts-new-local.md)。
8
9> **说明:**
10>
11> 从API version 12开始,在\@ComponentV2装饰的自定义组件中支持使用\@Param装饰器。
12>
13
14## 概述
15
16\@Param表示组件从外部传入的状态,使得父子组件之间的数据能够进行同步:
17
18- \@Param装饰的变量支持本地初始化,但是不允许在组件内部直接修改变量本身。
19
20- 被\@Param装饰的变量能够在初始化自定义组件时从外部传入,当数据源也是状态变量时,数据源的修改会同步给\@Param。
21- \@Param可以接受任意类型的数据源,包括普通变量、状态变量、常量、函数返回值等。
22- \@Param装饰的变量变化时,会刷新该变量关联的组件。
23- \@Param支持观测number、boolean、string、Object、class等基本类型以及Array、Set、Map、Date等内嵌类型。
24- 对于复杂类型如类对象,\@Param会接受数据源的引用。在组件内可以修改类对象中的属性,该修改会同步到数据源。
25- \@Param的观测能力仅限于被装饰的变量本身。当装饰简单类型时,对变量的整体改变能够观测到;当装饰对象类型时,仅能观测对象整体的改变;当装饰数组类型时,能观测到数组整体以及数组元素项的改变;当装饰Array、Set、Map、Date等内嵌类型时,可以观测到通过API调用带来的变化。详见[观察变化](#观察变化)。
26- \@Param支持null、undefined以及联合类型。
27
28
29## 状态管理V1版本接受外部传入的装饰器的局限性
30状态管理V1存在多种可接受外部传入的装饰器,常用的有\@State、\@Prop、\@Link、\@ObjectLink。这些装饰器使用各有限制,不易区分,当使用不当时,还会导致性能问题。
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
86在上面的示例中,\@State仅能在初始化时获得info的引用,当改变info之后,无法进行同步。\@Prop虽然能够进行单向同步,但是对于较复杂的类型来说,深拷贝性能较差。\@Link能够接受传入的引用进行双向同步,但它必须要求数据源也是状态变量,因此无法接受info中的成员属性region。\@ObjectLink能够接受类成员属性,但是要求该属性类型必须为\@Observed装饰的类。装饰器的不同限制使得父子组件之间传值规则十分复杂,不易使用。因此推出\@Param装饰器表示组件从外部传入的状态。
87
88## 装饰器说明
89
90| \@Param变量装饰器  | 说明                                                         |
91| ------------------ | ------------------------------------------------------------ |
92| 装饰器参数         | 无。                                                         |
93| 能否本地修改       | 否,修改值需使用[\@Event](./arkts-new-event.md)装饰器的能力。                        |
94| 同步类型           | 由父到子单向同步。                                           |
95| 允许装饰的变量类型 | Object、class、string、number、boolean、enum等基本类型以及Array、Date、Map、Set等内嵌类型。支持null、undefined以及联合类型。 |
96| 被装饰变量的初始值 | 允许本地初始化,若不在本地初始化,则需要和[\@Require](./arkts-require.md)装饰器一起使用,要求必须从外部传入初始化。 |
97
98## 变量传递
99
100| 传递规则       | 说明                                                         |
101| -------------- | ------------------------------------------------------------ |
102| 从父组件初始化 | \@Param装饰的变量允许本地初始化,若无本地初始化则必须从外部传入初始化。当同时存在本地初始值与外部传入值时,会优先使用外部传入值进行初始化。 |
103| 初始化子组件   | \@Param装饰的变量可以初始化子组件中\@Param装饰的变量。       |
104| 同步           | \@Param可以和父组件传入的状态变量数据源(即\@Local或\@Param装饰的变量)进行同步,当数据源发生变化时,会将修改同步给子组件的\@Param。 |
105
106## 观察变化
107
108使用\@Param装饰的变量具有被观测变化的能力。当装饰的变量发生变化时,会触发该变量绑定的UI组件刷新。
109
110- 当装饰的变量类型为boolean、string、number类型时,可以观察来自数据源同步的变化。
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            // 对数据源的更改会同步给子组件
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- 当装饰的变量类型为类对象时,仅可以观察到对类对象整体赋值的变化,无法直接观察到对类成员属性赋值的变化,对类成员属性的观察依赖\@ObservedV2和\@Trace装饰器。
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            // 对类对象整体的修改均能观察到
182            this.rawObject = new RawObject("new rawObject");
183            this.observedObject = new ObservedObject("new observedObject");
184        })
185        Button("change name")
186          .onClick(() => {
187            // @Local与@Param均不具备观察类对象属性的能力,因此对rawObject.name的修改无法观察到
188            this.rawObject.name = "new rawObject name";
189            // 由于ObservedObject的name属性被@Trace装饰,因此对observedObject.name的修改能被观察到
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- 当装饰的变量类型为简单类型的数组时,可以观察到数组整体或数组项的变化。
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- 当装饰的变量是嵌套类或对象数组时,\@Param无法观察深层对象属性的变化。对深层对象属性的观测依赖\@ObservedV2与\@Trace装饰器。
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            // 由于属性name被@Trace装饰,所以能够观察到
306            this.infoArr[0].name = "Win";
307          })
308        Button("change originInfo")
309          .onClick(() => {
310            // 由于变量originInfo被@Local装饰,所以能够观察到
311            this.originInfo = new Info("Origin", 100, 100);
312          })
313        Button("change originInfo region")
314          .onClick(() => {
315            // 由于属性x、y被@Trace装饰,所以能够观察到
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- 当装饰的变量类型是内置类型时,可以观察到变量整体赋值以及通过API调用带来的变化。
345
346  | 类型  | 可观测变化的API                                              |
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## 限制条件
354
355\@Param装饰器存在以下使用限制:
356
357- \@Param装饰器只能在\@ComponentV2装饰器的自定义组件中使用。
358
359  ```ts
360  @ComponentV2
361  struct MyComponent {
362    @Param message: string = "Hello World"; // 正确用法
363    build() {
364    }
365  }
366  @Component
367  struct TestComponent {
368    @Param message: string = "Hello World"; // 错误用法,编译时报错
369    build() {
370    }
371  }
372  ```
373
374- \@Param装饰的变量表示组件外部输入,需要被初始化。支持使用本地初始值做初始化。当存在外部传入值时,将优先使用外部传入的值初始化。既不使用本地初始值,也不使用外部传入值是不允许的。
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; // 错误用法,外部未传入初始化且本地也无初始值,编译报错
383    build() {
384      Column() {
385        Text(`${this.param1}`) // 本地初始化,显示Initialize local
386        Text(`${this.param2}`) // 外部传入初始化,显示Put in
387        Text(`${this.param3}`) // 外部传入初始化,显示Put in
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- \@Param装饰的变量在子组件中无法进行修改。但当装饰的变量类型为对象时,在子组件中修改对象中属性是允许的。
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            // 父组件更改@Local变量,会同步子组件对应@Param变量
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            // 错误用法,不允许在子组件更改@Param变量,编译时报错
441            this.info = new Info("Jack");
442          })
443        Button("Child change info.name")
444          .onClick(() => {
445            // 允许在子组件中更改对象中属性,该修改会同步到父组件数据源上,当属性被@Trace装饰时,可观测到对应UI刷新
446            this.info.name = "Jack";
447          })
448      }
449    }
450  }
451  ```
452
453## 使用场景
454
455### 从父组件到子组件变量传递与同步
456
457\@Param能够接受父组件\@Local或\@Param传递的数据并与之变化同步。
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### 装饰Date类型变量
521
522\@Param装饰Date类型变量,可以观察到数据源对Date整体的赋值,以及调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `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### 装饰Map类型变量
574
575\@Param装饰Map类型变量,可以观察到数据源对Map整体的赋值,以及调用Map的接口 set、clear、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### 装饰Set类型变量
625
626\@Param装饰Set类型变量,可以观察到数据源对Set整体的赋值,以及调用Set的接口 add、clear、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### 联合类型
673
674\@Param支持null、undefined以及联合类型。在下面的示例中,count类型为number | undefined,点击改变count的类型,UI会随之刷新。
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```