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```