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