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