1# \@Prop装饰器:父子单向同步 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @jiyujia926--> 5<!--Designer: @s10021109--> 6<!--Tester: @TerryTsao--> 7<!--Adviser: @zhang_yixin13--> 8 9\@Prop装饰的变量可以和父组件建立单向同步关系。 10 11在阅读\@Prop文档前,建议开发者首先了解[\@State](./arkts-state.md)的基本用法。最佳实践请参考[状态管理最佳实践](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-status-management)。 12 13> **说明:** 14> 15> 从API version 9开始,该装饰器支持在ArkTS卡片中使用。 16> 17> 从API version 11开始,该装饰器支持在原子化服务中使用。 18 19## 概述 20 21\@Prop装饰的变量具有以下特性: 22 23- \@Prop装饰的变量允许本地修改,但修改不会同步回父组件。 24 25- 当数据源更改时,\@Prop装饰的变量都会更新,并且会覆盖本地所有更改。 26 27## 装饰器使用规则说明 28 29| \@Prop变量装饰器 | 说明 | 30| ----------- | ---------------------------------------- | 31| 装饰器参数 | 无。 | 32| 同步类型 | 单向同步。对父组件状态变量值的修改,将同步给子组件\@Prop装饰的变量,子组件\@Prop装饰的变量的修改不会同步到父组件的状态变量上。<br>嵌套类型的场景请参考[观察变化](#观察变化)。 | 33| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>API version 10开始支持[Date类型](#装饰date类型变量)。<br/>API version 11及以上支持[Map](#装饰map类型变量)、[Set](#装饰set类型变量)类型、undefined和null类型、ArkUI框架定义的联合类型[Length](../../reference/apis-arkui/arkui-ts/ts-types.md#length)、[ResourceStr](../../reference/apis-arkui/arkui-ts/ts-types.md#resourcestr)、[ResourceColor](../../reference/apis-arkui/arkui-ts/ts-types.md#resourcecolor)类型以及这些类型的联合类型,示例见[Prop支持联合类型实例](#prop支持联合类型实例)。<br/>支持类型的场景请参考[观察变化](#观察变化)。| 34| 不允许装饰的变量类型 | 不支持装饰Function类型。 | 35| 嵌套传递层数 | 在组件复用场景,建议@Prop深度嵌套数据不要超过5层,嵌套太多会导致深拷贝占用的空间过大以及GarbageCollection(垃圾回收),引起性能问题,此时更建议使用[\@ObjectLink](arkts-observed-and-objectlink.md)。 | 36| 被装饰变量的初始值 | 允许本地初始化。API version 11及以上,如果和[\@Require](arkts-require.md)结合使用,则必须父组件构造传参。 | 37 38 39## 变量的传递/访问规则说明 40 41| 装饰器使用规则 | 说明 | 42| ------------------ | ------------------------------------------------------------ | 43| 从父组件初始化 | 如果本地有初始化,则是可选的,初始化行为和[\@State](./arkts-state.md#变量的传递访问规则说明)保持一致。没有的话,则必选,支持父组件中的常规变量(常规变量对@Prop赋值,只是数值的初始化,常规变量的变化不会触发UI刷新。只有状态变量才能触发UI刷新)、[\@State](arkts-state.md)、[\@Link](arkts-link.md)、\@Prop、[\@Provide](arkts-provide-and-consume.md)、[\@Consume](arkts-provide-and-consume.md)、[\@ObjectLink](arkts-observed-and-objectlink.md)、[\@StorageLink](arkts-appstorage.md#storagelink)、[\@StorageProp](arkts-appstorage.md#storageprop)、[\@LocalStorageLink](arkts-localstorage.md#localstoragelink)和[\@LocalStorageProp](arkts-localstorage.md#localstorageprop)去初始化子组件中的\@Prop变量。 | 44|用于初始化子组件| \@Prop支持初始化子组件中的常规变量、\@State、\@Link、\@Prop、\@Provide。| 45| 是否支持组件外访问 | \@Prop装饰的变量是私有的,只能在组件内访问。 | 46 47 初始化规则图示: 48 49 50 51## 观察变化和行为表现 52 53### 观察变化 54 55\@Prop装饰的数据可以观察到以下变化。 56 57- 当装饰支持类型,可以观察到赋值的变化。 58 59 ```ts 60 // 简单类型 61 @Prop count: number; 62 // 赋值的变化可以被观察到 63 this.count = 1; 64 // 复杂类型 65 @Prop title: Model; 66 // 可以观察到赋值的变化 67 this.title = new Model('Hi'); 68 ``` 69 70- 当装饰的类型是Object或者class复杂类型时,可以观察到自身的赋值和第一层的属性的变化,属性即object.keys(observedObject)返回的所有属性。 71 72```ts 73class Info { 74 public value: string; 75 constructor(value: string) { 76 this.value = value; 77 } 78} 79class Model { 80 public value: string; 81 public info: Info; 82 constructor(value: string, info: Info) { 83 this.value = value; 84 this.info = info; 85 } 86} 87 88@Prop title: Model; 89// 可以观察到第一层的变化 90this.title.value = 'Hi'; 91// 观察不到第二层的变化 92this.title.info.value = 'ArkUI'; 93``` 94 95对于嵌套场景,如果class是被\@Observed装饰的,可以观察到class属性的变化,示例请参考[@Prop嵌套场景](#prop嵌套场景)。 96 97- 当装饰的类型是数组的时候,可以观察到数组本身的赋值和数组项的添加、删除和更新。 98 99```ts 100// @State装饰的对象为数组时 101@Prop title: string[]; 102// 数组自身的赋值可以观察到 103this.title = ['1']; 104// 数组项的赋值可以观察到 105this.title[0] = '2'; 106// 删除数组项可以观察到 107this.title.pop(); 108// 新增数组项可以观察到 109this.title.push('3'); 110``` 111 112对于\@State和\@Prop的同步场景: 113 114- 使用父组件中\@State变量的值初始化子组件中的\@Prop变量。当\@State变量变化时,该变量值也会同步更新至\@Prop变量。 115- \@Prop装饰的变量的修改不会影响其数据源\@State装饰变量的值。 116- 除了\@State,数据源也可以用\@Link或\@Prop装饰,对\@Prop的同步机制是相同的。 117- 数据源和\@Prop变量的类型需要相同,\@Prop允许简单类型和class类型。 118 119- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性,详见[装饰Date类型变量](#装饰date类型变量)。 120 121- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。 122 123- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。 124 125### 框架行为 126 127理解\@Prop变量值初始化和更新机制,需要了解父组件和子组件的渲染和更新流程。 128 1291. 初始渲染: 130 1. 执行父组件的build()函数,创建子组件的新实例并传递数据源。 131 2. 初始化子组件\@Prop装饰的变量。 132 1332. 更新: 134 1. 子组件\@Prop更新时,更新仅停留在当前子组件,不会同步回父组件。 135 2. 当父组件的数据源更新时,子组件的\@Prop装饰的变量将被来自父组件的数据源重置,所有@Prop装饰变量的本地修改将被父组件的更新覆盖。 136 137> **说明:** 138> 139> \@Prop同步数据源依赖于数据源所在组件的刷新,而应用进入后台后无法触发刷新,因此应用进入后台后,@Prop无法从数据源更新。在此场景下,若需即时数据同步,推荐使用@Link代替。 140 141以下示例中,当@State装饰的变量message改变时,Father组件会刷新。由于Son组件使用@Prop接收了该变量,因此Father组件刷新的过程中会使用message的最新值去更新@Prop的值。@Prop更新后,会触发Son组件的刷新。 142 143```ts 144@Component 145struct Son { 146 @Prop message: string = 'Hi'; 147 148 build() { 149 Column() { 150 Text(this.message) 151 } 152 } 153} 154 155@Entry 156@Component 157struct Father { 158 @State message: string = 'Hello'; 159 160 build() { 161 Column() { 162 Text(this.message) 163 Button(`father click`).onClick(() => { 164 this.message += '*'; 165 }) 166 Son({ message: this.message }) 167 } 168 } 169} 170``` 171 172## 限制条件 173 174- \@Prop装饰变量时会进行深拷贝,在拷贝的过程中除了基本类型、Map、Set、Date、Array外,都会丢失类型。例如[PixelMap](../../reference/apis-image-kit/arkts-apis-image-PixelMap.md)等通过NAPI提供的复杂类型,由于有部分实现在Native侧,因此无法在ArkTS侧通过深拷贝获得完整的数据。 175 176## 使用场景 177 178### 父组件\@State到子组件\@Prop简单数据类型同步 179 180以下示例是\@State到子组件\@Prop简单数据同步,父组件ParentComponent的状态变量countDownStartValue初始化子组件CountDownComponent中\@Prop装饰的count,点击“Try again”,count的修改仅保留在CountDownComponent,不会同步给父组件ParentComponent。 181 182ParentComponent的状态变量countDownStartValue的变化将重置CountDownComponent的count。 183 184```ts 185@Component 186struct CountDownComponent { 187 @Prop count: number = 0; 188 costOfOneAttempt: number = 1; 189 190 build() { 191 Column() { 192 if (this.count > 0) { 193 Text(`You have ${this.count} Nuggets left`) 194 } else { 195 Text('Game over!') 196 } 197 // @Prop装饰的变量不会同步给父组件 198 Button(`Try again`).onClick(() => { 199 this.count -= this.costOfOneAttempt; 200 }) 201 } 202 } 203} 204 205@Entry 206@Component 207struct ParentComponent { 208 @State countDownStartValue: number = 10; 209 210 build() { 211 Column() { 212 Text(`Grant ${this.countDownStartValue} nuggets to play.`) 213 // 父组件的数据源的修改会同步给子组件 214 Button(`+1 - Nuggets in New Game`).onClick(() => { 215 this.countDownStartValue += 1; 216 }) 217 // 父组件的修改会同步给子组件 218 Button(`-1 - Nuggets in New Game`).onClick(() => { 219 this.countDownStartValue -= 1; 220 }) 221 222 CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 }) 223 } 224 } 225} 226``` 227 228在上面的示例中: 229 2301. CountDownComponent子组件首次创建时其\@Prop装饰的count变量将从父组件\@State装饰的countDownStartValue变量初始化。 231 2322. 按“+1”或“-1”按钮时,父组件的\@State装饰的countDownStartValue值会变化,这将触发父组件重新渲染,在父组件重新渲染过程中会刷新使用countDownStartValue状态变量的UI组件,并单向同步更新CountDownComponent子组件中的count值。 233 2343. 更新count状态变量值也会触发CountDownComponent的重新渲染,在重新渲染过程中,评估使用count状态变量的if语句条件(this.count > 0),并执行true分支中的使用count状态变量的UI组件相关描述来更新Text组件的UI显示。 235 2364. 当按下子组件CountDownComponent的“Try again”按钮时,其\@Prop变量count将被更改,但是count值的更改不会影响父组件的countDownStartValue值。 237 2385. 父组件的countDownStartValue值变化时,父组件的修改将覆盖掉子组件CountDownComponent中count本地的修改。 239 240### 父组件\@State数组项到子组件\@Prop简单数据类型同步 241 242父组件中@State如果装饰数组类型的变量,其数组项也可以初始化@Prop。以下示例中,父组件Index中@State装饰数组arr,将其数组项初始化子组件Child中@Prop装饰的value。 243 244```ts 245@Component 246struct Child { 247 @Prop value: number = 0; 248 249 build() { 250 Text(`${this.value}`) 251 .fontSize(50) 252 .onClick(() => { 253 this.value++; 254 }) 255 } 256} 257 258@Entry 259@Component 260struct Index { 261 @State arr: number[] = [1, 2, 3]; 262 263 build() { 264 Row() { 265 Column() { 266 Child({ value: this.arr[0] }) 267 Child({ value: this.arr[1] }) 268 Child({ value: this.arr[2] }) 269 270 Divider().height(5) 271 272 ForEach(this.arr, 273 (item: number) => { 274 Child({ value: item }) 275 }, 276 (item: number) => item.toString() 277 ) 278 Text('replace entire arr') 279 .fontSize(50) 280 .onClick(() => { 281 // 两个数组都包含项“3”。 282 this.arr = this.arr[0] == 1 ? [3, 4, 5] : [1, 2, 3]; 283 }) 284 } 285 } 286 } 287} 288``` 289 290初始渲染创建6个子组件实例,每个\@Prop装饰的变量初始化都在本地拷贝了一份数组项。子组件onClick事件处理程序会更改局部变量值。 291 292如果点击界面上的“1”六次,“2”五次、“3”四次,将所有变量的本地取值都变为“7”。 293 294``` 2957 2967 2977 298—————— 2997 3007 3017 302``` 303 304点击replace entire arr后,屏幕将显示以下信息。 305 306``` 3073 3084 3095 310—————— 3117 3124 3135 314``` 315 316- 在子组件Child中做的所有的修改都不会同步回父组件Index组件,所以即使6个组件显示都为7,但在父组件Index中,this.arr保存的值依旧是[1,2,3]。 317 318- 点击replace entire arr,this.arr[0] == 1成立,将this.arr赋值为[3, 4, 5]。 319 320- 因为this.arr[0]已更改,Child({value: this.arr[0]})组件将this.arr[0]更新同步到实例\@Prop装饰的变量。Child({value: this.arr[1]})和Child({value: this.arr[2]})的情况也类似。 321 322- this.arr的更改触发ForEach更新,this.arr更新的前后都有数值为3的数组项:[3, 4, 5] 和[1, 2, 3]。根据diff算法,数组项“3”将被保留,删除“1”和“2”的数组项,添加为“4”和“5”的数组项。这就意味着,数组项“3”的组件不会重新生成,而是将其移动到第一位。所以“3”对应的组件不会更新,此时“3”对应的组件数值为“7”,ForEach最终的渲染结果是“7”,“4”,“5”。 323 324### 从父组件中的\@State类对象属性到\@Prop简单类型的同步 325 326如果图书馆有一本图书和两位用户,每位用户都可以将图书标记为已读,此标记行为不会影响其他用户。从代码角度讲,对\@Prop图书对象的本地更改不会同步给图书馆组件中的\@State图书对象。 327 328在此示例中,图书类可以使用\@Observed装饰器,但不是必须的,只有在嵌套结构时需要此装饰器。这一点会在[从父组件中的\@State数组项到\@Prop class类型的同步](#从父组件中的state数组项到prop-class类型的同步)说明。 329 330```ts 331class Book { 332 public title: string; 333 public pages: number; 334 public readIt: boolean = false; 335 336 constructor(title: string, pages: number) { 337 this.title = title; 338 this.pages = pages; 339 } 340} 341 342@Component 343struct ReaderComp { 344 @Prop book: Book = new Book('', 0); 345 346 build() { 347 Row() { 348 Text(this.book.title) 349 Text(`...has${this.book.pages} pages!`) 350 Text(`...${this.book.readIt ? 'I have read' : 'I have not read it'}`) 351 .onClick(() => this.book.readIt = true) 352 } 353 } 354} 355 356@Entry 357@Component 358struct Library { 359 @State book: Book = new Book('100 secrets of C++', 765); 360 361 build() { 362 Column() { 363 ReaderComp({ book: this.book }) 364 ReaderComp({ book: this.book }) 365 } 366 } 367} 368``` 369 370### 从父组件中的\@State数组项到\@Prop class类型的同步 371 372以下示例中,更改了\@State装饰的allBooks数组中Book对象的属性,但点击“Mark read for everyone”时,没有触发UI更新。这是因为该属性是第二层的嵌套属性,\@State装饰器只能观察到第一层属性,不会观察到此属性更改,所以框架不会更新ReaderComp。 373 374```ts 375let nextId: number = 1; 376 377// @Observed 378class Book { 379 public id: number; 380 public title: string; 381 public pages: number; 382 public readIt: boolean = false; 383 384 constructor(title: string, pages: number) { 385 this.id = nextId++; 386 this.title = title; 387 this.pages = pages; 388 } 389} 390 391@Component 392struct ReaderComp { 393 @Prop book: Book = new Book('', 1); 394 395 build() { 396 Row() { 397 Text(` ${this.book ? this.book.title : 'Book is undefined'}`).fontColor('#e6000000') 398 Text(` has ${this.book ? this.book.pages : 'Book is undefined'} pages!`).fontColor('#e6000000') 399 Text(` ${this.book ? this.book.readIt ? 'I have read' : 'I have not read it' : 'Book is undefined'}`) 400 .fontColor('#e6000000') 401 .onClick(() => this.book.readIt = true) 402 } 403 } 404} 405 406@Entry 407@Component 408struct Library { 409 @State allBooks: Book[] = [new Book('C#', 765), new Book('JS', 652), new Book('TS', 765)]; 410 411 build() { 412 Column() { 413 Text('library`s all time favorite') 414 .width(312) 415 .height(40) 416 .backgroundColor('#0d000000') 417 .borderRadius(20) 418 .margin(12) 419 .padding({ left: 20 }) 420 .fontColor('#e6000000') 421 ReaderComp({ book: this.allBooks[2] }) 422 .backgroundColor('#0d000000') 423 .width(312) 424 .height(40) 425 .padding({ left: 20, top: 10 }) 426 .borderRadius(20) 427 .colorBlend('#e6000000') 428 Text('Books on loan to a reader') 429 .width(312) 430 .height(40) 431 .backgroundColor('#0d000000') 432 .borderRadius(20) 433 .margin(12) 434 .padding({ left: 20 }) 435 .fontColor('#e6000000') 436 ForEach(this.allBooks, (book: Book) => { 437 ReaderComp({ book: book }) 438 .margin(12) 439 .width(312) 440 .height(40) 441 .padding({ left: 20, top: 10 }) 442 .backgroundColor('#0d000000') 443 .borderRadius(20) 444 }, 445 (book: Book) => book.id.toString()) 446 Button('Add new') 447 .width(312) 448 .height(40) 449 .margin(12) 450 .fontColor('#FFFFFF') 451 .onClick(() => { 452 this.allBooks.push(new Book('JA', 512)); 453 }) 454 Button('Remove first book') 455 .width(312) 456 .height(40) 457 .margin(12) 458 .fontColor('#FFFFFF') 459 .onClick(() => { 460 if (this.allBooks.length > 0) { 461 this.allBooks.shift(); 462 } else { 463 console.info('length <= 0'); 464 } 465 }) 466 Button('Mark read for everyone') 467 .width(312) 468 .height(40) 469 .margin(12) 470 .fontColor('#FFFFFF') 471 .onClick(() => { 472 this.allBooks.forEach((book) => book.readIt = true) 473 }) 474 } 475 } 476} 477``` 478 479使用\@Observed装饰class Book,Book的属性变化将被观察。需要注意的是,\@Prop在子组件装饰的状态变量和父组件的数据源是单向同步关系,即ReaderComp中的\@Prop book的修改不会同步给父组件Library。而父组件只会在状态变量发生变化的时候,才会触发UI的重新渲染。 480 481```ts 482@Observed 483class Book { 484 public id: number; 485 public title: string; 486 public pages: number; 487 public readIt: boolean = false; 488 489 constructor(title: string, pages: number) { 490 this.id = nextId++; 491 this.title = title; 492 this.pages = pages; 493 } 494} 495``` 496 497\@Observed装饰的类的实例会被不透明的代理对象包装,此代理可以检测到包装对象内的所有属性更改。如果发生这种情况,此时,代理通知\@Prop,\@Prop对象值被更新。 498 499 500 501### \@Prop本地初始化不和父组件同步 502 503为了支持\@Component装饰的组件复用场景,\@Prop支持本地初始化,这样可以让\@Prop是否与父组件建立同步关系变得可选。当且仅当\@Prop有本地初始化时,从父组件向子组件传递\@Prop的数据源才是可选的。 504 505下面的示例中,子组件包含两个\@Prop变量: 506 507- \@Prop customCounter没有本地初始化,所以需要父组件提供数据源去初始化\@Prop,并当父组件的数据源变化时,\@Prop也将被更新。 508 509- \@Prop customCounter2有本地初始化,在这种情况下,\@Prop依旧允许但非强制父组件同步数据源给\@Prop。 510 511```ts 512@Component 513struct MyComponent { 514 @Prop customCounter: number; 515 @Prop customCounter2: number = 5; 516 517 build() { 518 Column() { 519 Row() { 520 Text(`From Main: ${this.customCounter}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 }) 521 } 522 523 Row() { 524 Button('Click to change locally!') 525 .width(288) 526 .height(40) 527 .margin({ left: 30, top: 12 }) 528 .fontColor('#FFFFFF') 529 .onClick(() => { 530 this.customCounter2++; 531 }) 532 } 533 534 Row() { 535 Text(`Custom Local: ${this.customCounter2}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 }) 536 } 537 } 538 } 539} 540 541@Entry 542@Component 543struct MainProgram { 544 @State mainCounter: number = 10; 545 546 build() { 547 Column() { 548 Row() { 549 Column() { 550 // customCounter必须从父组件初始化,因为MyComponent的customCounter成员变量缺少本地初始化;此处,customCounter2可以不做初始化。 551 MyComponent({ customCounter: this.mainCounter }) 552 // customCounter2也可以从父组件初始化,父组件初始化的值会覆盖子组件customCounter2的本地初始化的值 553 MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter }) 554 } 555 } 556 557 Row() { 558 Column() { 559 Button('Click to change number') 560 .width(288) 561 .height(40) 562 .margin({ left: 30, top: 12 }) 563 .fontColor('#FFFFFF') 564 .onClick(() => { 565 this.mainCounter++; 566 }) 567 } 568 } 569 } 570 } 571} 572``` 573 574 575 576### \@Prop嵌套场景 577 578在嵌套场景下,每一层都要用\@Observed装饰,且每一层都要被\@Prop接收,这样才能观察到嵌套场景。 579 580```ts 581// 以下是嵌套类对象的数据结构。 582@Observed 583class Son { 584 public title: string; 585 586 constructor(title: string) { 587 this.title = title; 588 } 589} 590 591@Observed 592class Father { 593 public name: string; 594 public son: Son; 595 596 constructor(name: string, son: Son) { 597 this.name = name; 598 this.son = son; 599 } 600} 601``` 602 603以下组件层次结构展示了\@Prop嵌套场景的数据结构。 604 605```ts 606@Entry 607@Component 608struct Person { 609 @State person: Father = new Father('Hello', new Son('world')); 610 611 build() { 612 Column() { 613 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) { 614 Button('change Father name') 615 .width(312) 616 .height(40) 617 .margin(12) 618 .fontColor('#FFFFFF') 619 .onClick(() => { 620 this.person.name = 'Hi'; 621 }) 622 Button('change Son title') 623 .width(312) 624 .height(40) 625 .margin(12) 626 .fontColor('#FFFFFF') 627 .onClick(() => { 628 this.person.son.title = 'ArkUI'; 629 }) 630 Text(this.person.name) 631 .fontSize(16) 632 .margin(12) 633 .width(312) 634 .height(40) 635 .backgroundColor('#ededed') 636 .borderRadius(20) 637 .textAlign(TextAlign.Center) 638 .fontColor('#e6000000') 639 .onClick(() => { 640 this.person.name = 'Bye'; 641 }) 642 Text(this.person.son.title) 643 .fontSize(16) 644 .margin(12) 645 .width(312) 646 .height(40) 647 .backgroundColor('#ededed') 648 .borderRadius(20) 649 .textAlign(TextAlign.Center) 650 .onClick(() => { 651 this.person.son.title = 'openHarmony'; 652 }) 653 Child({ child: this.person.son }) 654 } 655 } 656 } 657} 658 659 660@Component 661struct Child { 662 @Prop child: Son = new Son(''); 663 664 build() { 665 Column() { 666 Text(this.child.title) 667 .fontSize(16) 668 .margin(12) 669 .width(312) 670 .height(40) 671 .backgroundColor('#ededed') 672 .borderRadius(20) 673 .textAlign(TextAlign.Center) 674 .onClick(() => { 675 this.child.title = 'Bye Bye'; 676 }) 677 } 678 } 679} 680``` 681 682 683 684### 装饰Map类型变量 685 686> **说明:** 687> 688> 从API version 11开始,\@Prop支持Map类型。 689 690在下面的示例中,value类型为Map\<number, string\>,点击Button改变message的值,视图会随之刷新。 691 692```ts 693@Component 694struct Child { 695 @Prop value: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]); 696 697 build() { 698 Column() { 699 ForEach(Array.from(this.value.entries()), (item: [number, string]) => { 700 Text(`${item[0]}`).fontSize(30) 701 Text(`${item[1]}`).fontSize(30) 702 Divider() 703 }) 704 Button('child init map').onClick(() => { 705 this.value = new Map([[0, 'a'], [1, 'b'], [3, 'c']]); 706 }) 707 Button('child set new one').onClick(() => { 708 this.value.set(4, 'd'); 709 }) 710 Button('child clear').onClick(() => { 711 this.value.clear(); 712 }) 713 Button('child replace the first one').onClick(() => { 714 this.value.set(0, 'aa'); 715 }) 716 Button('child delete the first one').onClick(() => { 717 this.value.delete(0); 718 }) 719 } 720 } 721} 722 723 724@Entry 725@Component 726struct MapSample { 727 @State message: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]); 728 729 build() { 730 Row() { 731 Column() { 732 Child({ value: this.message }) 733 } 734 .width('100%') 735 } 736 .height('100%') 737 } 738} 739``` 740 741### 装饰Set类型变量 742 743> **说明:** 744> 745> 从API version 11开始,\@Prop支持Set类型。 746 747在下面的示例中,message类型为Set\<number\>,点击Button改变message的值,视图会随之刷新。 748 749```ts 750@Component 751struct Child { 752 @Prop message: Set<number> = new Set([0, 1, 2, 3, 4]); 753 754 build() { 755 Column() { 756 ForEach(Array.from(this.message.entries()), (item: [number, number]) => { 757 Text(`${item[0]}`).fontSize(30) 758 Divider() 759 }) 760 Button('init set').onClick(() => { 761 this.message = new Set([0, 1, 2, 3, 4]); 762 }) 763 Button('set new one').onClick(() => { 764 this.message.add(5); 765 }) 766 Button('clear').onClick(() => { 767 this.message.clear(); 768 }) 769 Button('delete the first one').onClick(() => { 770 this.message.delete(0); 771 }) 772 } 773 .width('100%') 774 } 775} 776 777 778@Entry 779@Component 780struct SetSample { 781 @State message: Set<number> = new Set([0, 1, 2, 3, 4]); 782 783 build() { 784 Row() { 785 Column() { 786 Child({ message: this.message }) 787 } 788 .width('100%') 789 } 790 .height('100%') 791 } 792} 793``` 794 795### 装饰Date类型变量 796 797在下面的示例中,selectedDate类型为Date,点击Button改变Date的值,视图会随之刷新。 798 799```ts 800@Component 801struct DateComponent { 802 @Prop selectedDate: Date = new Date(''); 803 804 build() { 805 Column() { 806 Button('child update the new date') 807 .margin(10) 808 .onClick(() => { 809 this.selectedDate = new Date('2023-09-09'); 810 }) 811 Button(`child increase the year by 1`).onClick(() => { 812 this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1); 813 }) 814 DatePicker({ 815 start: new Date('1970-1-1'), 816 end: new Date('2100-1-1'), 817 selected: this.selectedDate 818 }) 819 } 820 } 821} 822 823@Entry 824@Component 825struct ParentComponent { 826 @State parentSelectedDate: Date = new Date('2021-08-08'); 827 828 build() { 829 Column() { 830 Button('parent update the new date') 831 .margin(10) 832 .onClick(() => { 833 this.parentSelectedDate = new Date('2023-07-07'); 834 }) 835 Button('parent increase the day by 1') 836 .margin(10) 837 .onClick(() => { 838 this.parentSelectedDate.setDate(this.parentSelectedDate.getDate() + 1); 839 }) 840 DatePicker({ 841 start: new Date('1970-1-1'), 842 end: new Date('2100-1-1'), 843 selected: this.parentSelectedDate 844 }) 845 846 DateComponent({ selectedDate: this.parentSelectedDate }) 847 } 848 849 } 850} 851``` 852 853### Prop支持联合类型实例 854 855@Prop支持联合类型和undefined和null,在下面的示例中,animal类型为Animals | undefined,点击父组件Zoo中的Button改变animal的属性或者类型,Child中也会对应刷新。 856 857```ts 858class Animals { 859 public name: string; 860 861 constructor(name: string) { 862 this.name = name; 863 } 864} 865 866@Component 867struct Child { 868 @Prop animal: Animals | undefined; 869 870 build() { 871 Column() { 872 Text(`Child's animal is ${this.animal instanceof Animals ? this.animal.name : 'undefined'}`).fontSize(30) 873 874 Button('Child change animals into tigers') 875 .onClick(() => { 876 // 赋值为Animals的实例 877 this.animal = new Animals('Tiger'); 878 }) 879 880 Button('Child change animal to undefined') 881 .onClick(() => { 882 // 赋值为undefined 883 this.animal = undefined; 884 }) 885 886 }.width('100%') 887 } 888} 889 890@Entry 891@Component 892struct Zoo { 893 @State animal: Animals | undefined = new Animals('lion'); 894 895 build() { 896 Column() { 897 Text(`Parents' animals are ${this.animal instanceof Animals ? this.animal.name : 'undefined'}`).fontSize(30) 898 899 Child({animal: this.animal}) 900 901 Button('Parents change animals into dogs') 902 .onClick(() => { 903 // 判断animal的类型,做属性的更新 904 if (this.animal instanceof Animals) { 905 this.animal.name = 'Dog'; 906 } else { 907 console.info('num is undefined, cannot change property'); 908 } 909 }) 910 911 Button('Parents change animal to undefined') 912 .onClick(() => { 913 // 赋值为undefined 914 this.animal = undefined; 915 }) 916 } 917 } 918} 919``` 920 921## 常见问题 922 923### \@Prop装饰状态变量未初始化错误 924 925\@Prop需要被初始化,如果没有进行本地初始化的,则必须通过父组件进行初始化。如果进行了本地初始化,那么是可以不通过父组件进行初始化的。 926 927【反例】 928 929```ts 930@Observed 931class Commodity { 932 public price: number = 0; 933 934 constructor(price: number) { 935 this.price = price; 936 } 937} 938 939@Component 940struct PropChild { 941 @Prop fruit: Commodity; // 未进行本地初始化 942 943 build() { 944 Text(`PropChild fruit ${this.fruit.price}`) 945 .onClick(() => { 946 this.fruit.price += 1; 947 }) 948 } 949} 950 951@Entry 952@Component 953struct Parent { 954 @State fruit: Commodity[] = [new Commodity(1)]; 955 956 build() { 957 Column() { 958 Text(`Parent fruit ${this.fruit[0].price}`) 959 .onClick(() => { 960 this.fruit[0].price += 1; 961 }) 962 963 // @Prop本地没有初始化,也没有从父组件初始化 964 PropChild() 965 } 966 } 967} 968``` 969 970【正例】 971 972```ts 973@Observed 974class Commodity { 975 public price: number = 0; 976 977 constructor(price: number) { 978 this.price = price; 979 } 980} 981 982@Component 983struct PropChild1 { 984 @Prop fruit: Commodity; // 未进行本地初始化 985 986 build() { 987 Text(`PropChild1 fruit ${this.fruit.price}`) 988 .onClick(() => { 989 this.fruit.price += 1; 990 }) 991 } 992} 993 994@Component 995struct PropChild2 { 996 @Prop fruit: Commodity = new Commodity(1); // 进行本地初始化 997 998 build() { 999 Text(`PropChild2 fruit ${this.fruit.price}`) 1000 .onClick(() => { 1001 this.fruit.price += 1; 1002 }) 1003 } 1004} 1005 1006@Entry 1007@Component 1008struct Parent { 1009 @State fruit: Commodity[] = [new Commodity(1)]; 1010 1011 build() { 1012 Column() { 1013 Text(`Parent fruit ${this.fruit[0].price}`) 1014 .onClick(() => { 1015 this.fruit[0].price += 1; 1016 }) 1017 1018 // @PropChild1本地没有初始化,必须从父组件初始化 1019 PropChild1({ fruit: this.fruit[0] }) 1020 // @PropChild2本地进行了初始化,可以不从父组件初始化,也可以从父组件初始化 1021 PropChild2() 1022 PropChild2({ fruit: this.fruit[0] }) 1023 } 1024 } 1025} 1026``` 1027 1028### 使用a.b(this.object)形式调用,不会触发UI刷新 1029 1030在build方法内,当@Prop装饰的变量是Object类型、且通过a.b(this.object)形式调用时,b方法内传入的是this.object的原始对象,修改其属性,无法触发UI刷新。如下例中,通过静态方法Score.changeScore1或者this.changeScore2修改自定义组件Child中的this.score.value时,UI不会刷新。 1031 1032【反例】 1033 1034```ts 1035class Score { 1036 value: number; 1037 constructor(value: number) { 1038 this.value = value; 1039 } 1040 1041 static changeScore1(param1:Score) { 1042 param1.value += 1; 1043 } 1044} 1045 1046@Entry 1047@Component 1048struct Parent { 1049 @State score: Score = new Score(1); 1050 1051 build() { 1052 Column({space:8}) { 1053 Text(`The value in Parent is ${this.score.value}.`) 1054 .fontSize(30) 1055 .fontColor(Color.Red) 1056 Child({ score: this.score }) 1057 } 1058 .width('100%') 1059 .height('100%') 1060 } 1061} 1062 1063@Component 1064struct Child { 1065 @Prop score: Score; 1066 1067 changeScore2(param2:Score) { 1068 param2.value += 2; 1069 } 1070 1071 build() { 1072 Column({space:8}) { 1073 Text(`The value in Child is ${this.score.value}.`) 1074 .fontSize(30) 1075 Button(`changeScore1`) 1076 .onClick(()=>{ 1077 // 通过静态方法调用,无法触发UI刷新 1078 Score.changeScore1(this.score); 1079 }) 1080 Button(`changeScore2`) 1081 .onClick(()=>{ 1082 // 使用this通过自定义组件内部方法调用,无法触发UI刷新 1083 this.changeScore2(this.score); 1084 }) 1085 } 1086 } 1087} 1088``` 1089 1090可以通过如下先赋值、再调用新赋值的变量的方式为this.score加上Proxy代理,实现UI刷新。 1091 1092【正例】 1093 1094```ts 1095class Score { 1096 value: number; 1097 constructor(value: number) { 1098 this.value = value; 1099 } 1100 1101 static changeScore1(score:Score) { 1102 score.value += 1; 1103 } 1104} 1105 1106@Entry 1107@Component 1108struct Parent { 1109 @State score: Score = new Score(1); 1110 1111 build() { 1112 Column({space:8}) { 1113 Text(`The value in Parent is ${this.score.value}.`) 1114 .fontSize(30) 1115 .fontColor(Color.Red) 1116 Child({ score: this.score }) 1117 } 1118 .width('100%') 1119 .height('100%') 1120 } 1121} 1122 1123@Component 1124struct Child { 1125 @Prop score: Score; 1126 1127 changeScore2(score:Score) { 1128 score.value += 2; 1129 } 1130 1131 build() { 1132 Column({space:8}) { 1133 Text(`The value in Child is ${this.score.value}.`) 1134 .fontSize(30) 1135 Button(`changeScore1`) 1136 .onClick(()=>{ 1137 // 通过赋值添加 Proxy 代理 1138 let score1 = this.score; 1139 Score.changeScore1(score1); 1140 }) 1141 Button(`changeScore2`) 1142 .onClick(()=>{ 1143 // 通过赋值添加 Proxy 代理 1144 let score2 = this.score; 1145 this.changeScore2(score2); 1146 }) 1147 } 1148 } 1149} 1150``` 1151<!--no_check--> 1152