1# \@Prop装饰器:父子单向同步 2 3 4\@Prop装饰的变量可以和父组件建立单向的同步关系。\@Prop装饰的变量是可变的,但是变化不会同步回其父组件。 5 6 7> **说明:** 8> 9> 从API version 9开始,该装饰器支持在ArkTS卡片中使用。 10 11 12## 概述 13 14\@Prop装饰的变量和父组件建立单向的同步关系: 15 16- \@Prop变量允许在本地修改,但修改后的变化不会同步回父组件。 17 18- 当数据源更改时,\@Prop装饰的变量都会更新,并且会覆盖本地所有更改。因此,数值的同步是父组件到子组件(所属组件),子组件数值的变化不会同步到父组件。 19 20 21 22## 限制条件 23 24- \@Prop装饰变量时会进行深拷贝,在拷贝的过程中除了基本类型、Map、Set、Date、Array外,都会丢失类型。例如[PixelMap](../reference/apis/js-apis-image.md#pixelmap7)等通过NAPI提供的复杂类型,由于有部分实现在Native侧,因此无法在ArkTS侧通过深拷贝获得完整的数据。 25 26- \@Prop装饰器不能在\@Entry装饰的自定义组件中使用。 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/>支持类型的场景请参考[观察变化](#观察变化)。<br/>必须指定类型。<br/>**说明** :<br/>不支持Length、ResourceStr、ResourceColor类型,Length,ResourceStr、ResourceColor为简单类型和复杂类型的联合类型。<br/>在父组件中,传递给\@Prop装饰的值不能为undefined或者null,反例如下所示。<br/>CompA ({ aProp: undefined })<br/>CompA ({ aProp: null })<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简单类型的同步)。 | 36| 嵌套传递层数 | 在组件复用场景,建议@Prop深度嵌套数据不要超过5层,嵌套太多会导致深拷贝占用的空间过大以及GarbageCollection(垃圾回收),引起性能问题,此时更建议使用[\@ObjectLink](arkts-observed-and-objectlink.md)。 | 37| 被装饰变量的初始值 | 允许本地初始化。 | 38 39 40## 变量的传递/访问规则说明 41 42| 传递/访问 | 说明 | 43| --------- | ---------------------------------------- | 44| 从父组件初始化 | 如果本地有初始化,则是可选的。没有的话,则必选,支持父组件中的常规变量(常规变量对@Prop赋值,只是数值的初始化,常规变量的变化不会触发UI刷新。只有状态变量才能触发UI刷新)、\@State、\@Link、\@Prop、\@Provide、\@Consume、\@ObjectLink、\@StorageLink、\@StorageProp、\@LocalStorageLink和\@LocalStorageProp去初始化子组件中的\@Prop变量。 | 45| 用于初始化子组件 | \@Prop支持去初始化子组件中的常规变量、\@State、\@Link、\@Prop、\@Provide。 | 46| 是否支持组件外访问 | \@Prop装饰的变量是私有的,只能在组件内访问。 | 47 48 49 **图1** 初始化规则图示 50 51 52![zh-cn_image_0000001552972029](figures/zh-cn_image_0000001552972029.png) 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``` 78class ClassA { 79 public value: string; 80 constructor(value: string) { 81 this.value = value; 82 } 83} 84class Model { 85 public value: string; 86 public a: ClassA; 87 constructor(value: string, a: ClassA) { 88 this.value = value; 89 this.a = a; 90 } 91} 92 93@Prop title: Model; 94// 可以观察到第一层的变化 95this.title.value = 'Hi' 96// 观察不到第二层的变化 97this.title.a.value = 'ArkUi' 98``` 99 100对于嵌套场景,如果class是被\@Observed装饰的,可以观察到class属性的变化,示例请参考[@Prop嵌套场景](#prop嵌套场景)。 101 102- 当装饰的类型是数组的时候,可以观察到数组本身的赋值、添加、删除和更新。 103 104``` 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### 框架行为 181 182要理解\@Prop变量值初始化和更新机制,有必要了解父组件和拥有\@Prop变量的子组件初始渲染和更新流程。 183 1841. 初始渲染: 185 1. 执行父组件的build()函数将创建子组件的新实例,将数据源传递给子组件; 186 2. 初始化子组件\@Prop装饰的变量。 187 1882. 更新: 189 1. 子组件\@Prop更新时,更新仅停留在当前子组件,不会同步回父组件; 190 2. 当父组件的数据源更新时,子组件的\@Prop装饰的变量将被来自父组件的数据源重置,所有\@Prop装饰的本地的修改将被父组件的更新覆盖。 191 192> **说明:** 193> 194> \@Prop装饰的数据更新依赖其所属自定义组件的重新渲染,所以在应用进入后台后,\@Prop无法刷新,推荐使用\@Link代替。 195 196 197## 使用场景 198 199 200### 父组件\@State到子组件\@Prop简单数据类型同步 201 202 203以下示例是\@State到子组件\@Prop简单数据同步,父组件ParentComponent的状态变量countDownStartValue初始化子组件CountDownComponent中\@Prop装饰的count,点击“Try again”,count的修改仅保留在CountDownComponent 不会同步给父组件ParentComponent。 204 205 206ParentComponent的状态变量countDownStartValue的变化将重置CountDownComponent的count。 207 208 209 210```ts 211@Component 212struct CountDownComponent { 213 @Prop count: number = 0; 214 costOfOneAttempt: number = 1; 215 216 build() { 217 Column() { 218 if (this.count > 0) { 219 Text(`You have ${this.count} Nuggets left`) 220 } else { 221 Text('Game over!') 222 } 223 // @Prop装饰的变量不会同步给父组件 224 Button(`Try again`).onClick(() => { 225 this.count -= this.costOfOneAttempt; 226 }) 227 } 228 } 229} 230 231@Entry 232@Component 233struct ParentComponent { 234 @State countDownStartValue: number = 10; 235 236 build() { 237 Column() { 238 Text(`Grant ${this.countDownStartValue} nuggets to play.`) 239 // 父组件的数据源的修改会同步给子组件 240 Button(`+1 - Nuggets in New Game`).onClick(() => { 241 this.countDownStartValue += 1; 242 }) 243 // 父组件的修改会同步给子组件 244 Button(`-1 - Nuggets in New Game`).onClick(() => { 245 this.countDownStartValue -= 1; 246 }) 247 248 CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 }) 249 } 250 } 251} 252``` 253 254 255在上面的示例中: 256 257 2581. CountDownComponent子组件首次创建时其\@Prop装饰的count变量将从父组件\@State装饰的countDownStartValue变量初始化; 259 2602. 按“+1”或“-1”按钮时,父组件的\@State装饰的countDownStartValue值会变化,这将触发父组件重新渲染,在父组件重新渲染过程中会刷新使用countDownStartValue状态变量的UI组件并单向同步更新CountDownComponent子组件中的count值; 261 2623. 更新count状态变量值也会触发CountDownComponent的重新渲染,在重新渲染过程中,评估使用count状态变量的if语句条件(this.count > 0),并执行true分支中的使用count状态变量的UI组件相关描述来更新Text组件的UI显示; 263 2644. 当按下子组件CountDownComponent的“Try again”按钮时,其\@Prop变量count将被更改,但是count值的更改不会影响父组件的countDownStartValue值; 265 2665. 父组件的countDownStartValue值会变化时,父组件的修改将覆盖掉子组件CountDownComponent中count本地的修改。 267 268 269### 父组件\@State数组项到子组件\@Prop简单数据类型同步 270 271 272父组件中\@State如果装饰的数组,其数组项也可以初始化\@Prop。以下示例中父组件Index中\@State装饰的数组arr,将其数组项初始化子组件Child中\@Prop装饰的value。 273 274 275 276```ts 277@Component 278struct Child { 279 @Prop value: number = 0; 280 281 build() { 282 Text(`${this.value}`) 283 .fontSize(50) 284 .onClick(() => { 285 this.value++ 286 }) 287 } 288} 289 290@Entry 291@Component 292struct Index { 293 @State arr: number[] = [1, 2, 3]; 294 295 build() { 296 Row() { 297 Column() { 298 Child({ value: this.arr[0] }) 299 Child({ value: this.arr[1] }) 300 Child({ value: this.arr[2] }) 301 302 Divider().height(5) 303 304 ForEach(this.arr, 305 (item: number) => { 306 Child({ value: item }) 307 }, 308 (item: string) => item.toString() 309 ) 310 Text('replace entire arr') 311 .fontSize(50) 312 .onClick(() => { 313 // 两个数组都包含项“3”。 314 this.arr = this.arr[0] == 1 ? [3, 4, 5] : [1, 2, 3]; 315 }) 316 } 317 } 318 } 319} 320``` 321 322 323初始渲染创建6个子组件实例,每个\@Prop装饰的变量初始化都在本地拷贝了一份数组项。子组件onclick事件处理程序会更改局部变量值。 324 325 326如果点击界面上的“1”六下、“2”五下、“3”四下,将所有变量的本地取值都变为“7”。 327 328 329 330``` 3317 3327 3337 334---- 3357 3367 3377 338``` 339 340 341单击replace entire arr后,屏幕将显示以下信息。 342 343 344 345``` 3463 3474 3485 349---- 3507 3514 3525 353``` 354 355 356- 在子组件Child中做的所有的修改都不会同步回父组件Index组件,所以即使6个组件显示都为7,但在父组件Index中,this.arr保存的值依旧是[1,2,3]。 357 358- 点击replace entire arr,this.arr[0] == 1成立,将this.arr赋值为[3, 4, 5]; 359 360- 因为this.arr[0]已更改,Child({value: this.arr[0]})组件将this.arr[0]更新同步到实例\@Prop装饰的变量。Child({value: this.arr[1]})和Child({value: this.arr[2]})的情况也类似。 361 362 363- 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”。 364 365 366### 从父组件中的\@State类对象属性到\@Prop简单类型的同步 367 368如果图书馆有一本图书和两位用户,每位用户都可以将图书标记为已读,此标记行为不会影响其它读者用户。从代码角度讲,对\@Prop图书对象的本地更改不会同步给图书馆组件中的\@State图书对象。 369 370在此示例中,图书类可以使用\@Observed装饰器,但不是必须的,只有在嵌套结构时需要此装饰器。这一点我们会在[从父组件中的@State数组项到@Prop class类型的同步](#从父组件中的state数组项到prop-class类型的同步)说明。 371 372 373```ts 374class Book { 375 public title: string; 376 public pages: number; 377 public readIt: boolean = false; 378 379 constructor(title: string, pages: number) { 380 this.title = title; 381 this.pages = pages; 382 } 383} 384 385@Component 386struct ReaderComp { 387 @Prop book: Book = new Book("", 0); 388 389 build() { 390 Row() { 391 Text(this.book.title) 392 Text(`...has${this.book.pages} pages!`) 393 Text(`...${this.book.readIt ? "I have read" : 'I have not read it'}`) 394 .onClick(() => this.book.readIt = true) 395 } 396 } 397} 398 399@Entry 400@Component 401struct Library { 402 @State book: Book = new Book('100 secrets of C++', 765); 403 404 build() { 405 Column() { 406 ReaderComp({ book: this.book }) 407 ReaderComp({ book: this.book }) 408 } 409 } 410} 411``` 412 413### 从父组件中的\@State数组项到\@Prop class类型的同步 414 415在下面的示例中,更改了\@State 装饰的allBooks数组中Book对象上的属性,但点击“Mark read for everyone”无反应。这是因为该属性是第二层的嵌套属性,\@State装饰器只能观察到第一层属性,不会观察到此属性更改,所以框架不会更新ReaderComp。 416 417```ts 418let nextId: number = 1; 419 420// @Observed 421class Book { 422 public id: number; 423 public title: string; 424 public pages: number; 425 public readIt: boolean = false; 426 427 constructor(title: string, pages: number) { 428 this.id = nextId++; 429 this.title = title; 430 this.pages = pages; 431 } 432} 433 434@Component 435struct ReaderComp { 436 @Prop book: Book = new Book("", 1); 437 438 build() { 439 Row() { 440 Text(` ${this.book ? this.book.title : "Book is undefined"}`).fontColor('#e6000000') 441 Text(` has ${this.book ? this.book.pages : "Book is undefined"} pages!`).fontColor('#e6000000') 442 Text(` ${this.book ? this.book.readIt ? "I have read" : 'I have not read it' : "Book is undefined"}`).fontColor('#e6000000') 443 .onClick(() => this.book.readIt = true) 444 } 445 } 446} 447 448@Entry 449@Component 450struct Library { 451 @State allBooks: Book[] = [new Book("C#", 765), new Book("JS", 652), new Book("TS", 765)]; 452 453 build() { 454 Column() { 455 Text('library`s all time favorite') 456 .width(312) 457 .height(40) 458 .backgroundColor('#0d000000') 459 .borderRadius(20) 460 .margin(12) 461 .padding({ left: 20 }) 462 .fontColor('#e6000000') 463 ReaderComp({ book: this.allBooks[2] }) 464 .backgroundColor('#0d000000') 465 .width(312) 466 .height(40) 467 .padding({ left: 20, top: 10 }) 468 .borderRadius(20) 469 .colorBlend('#e6000000') 470 Divider() 471 Text('Books on loaan to a reader') 472 .width(312) 473 .height(40) 474 .backgroundColor('#0d000000') 475 .borderRadius(20) 476 .margin(12) 477 .padding({ left: 20 }) 478 .fontColor('#e6000000') 479 ForEach(this.allBooks, (book: Book) => { 480 ReaderComp({ book: book }) 481 .margin(12) 482 .width(312) 483 .height(40) 484 .padding({ left: 20, top: 10 }) 485 .backgroundColor('#0d000000') 486 .borderRadius(20) 487 }, 488 (book: Book) => book.id.toString()) 489 Button('Add new') 490 .width(312) 491 .height(40) 492 .margin(12) 493 .fontColor('#FFFFFF 90%') 494 .onClick(() => { 495 this.allBooks.push(new Book("JA", 512)); 496 }) 497 Button('Remove first book') 498 .width(312) 499 .height(40) 500 .margin(12) 501 .fontColor('#FFFFFF 90%') 502 .onClick(() => { 503 if (this.allBooks.length > 0){ 504 this.allBooks.shift(); 505 } else { 506 console.log("length <= 0") 507 } 508 }) 509 Button("Mark read for everyone") 510 .width(312) 511 .height(40) 512 .margin(12) 513 .fontColor('#FFFFFF 90%') 514 .onClick(() => { 515 this.allBooks.forEach((book) => book.readIt = true) 516 }) 517 } 518 } 519} 520``` 521 522 需要使用\@Observed装饰class Book,Book的属性将被观察。 需要注意的是,\@Prop在子组件装饰的状态变量和父组件的数据源是单向同步关系,即ReaderComp中的\@Prop book的修改不会同步给父组件Library。而父组件只会在数值有更新的时候(和上一次状态的对比),才会触发UI的重新渲染。 523 524```ts 525@Observed 526class Book { 527 public id: number; 528 public title: string; 529 public pages: number; 530 public readIt: boolean = false; 531 532 constructor(title: string, pages: number) { 533 this.id = nextId++; 534 this.title = title; 535 this.pages = pages; 536 } 537} 538``` 539 540\@Observed装饰的类的实例会被不透明的代理对象包装,此代理可以检测到包装对象内的所有属性更改。如果发生这种情况,此时,代理通知\@Prop,\@Prop对象值被更新。 541 542![Video-prop-UsageScenario-one](figures/Video-prop-UsageScenario-one.gif) 543 544### \@Prop本地初始化不和父组件同步 545 546为了支持\@Component装饰的组件复用场景,\@Prop支持本地初始化,这样可以让\@Prop是否与父组件建立同步关系变得可选。当且仅当\@Prop有本地初始化时,从父组件向子组件传递\@Prop的数据源才是可选的。 547 548下面的示例中,子组件包含两个\@Prop变量: 549 550- \@Prop customCounter没有本地初始化,所以需要父组件提供数据源去初始化\@Prop,并当父组件的数据源变化时,\@Prop也将被更新; 551 552- \@Prop customCounter2有本地初始化,在这种情况下,\@Prop依旧允许但非强制父组件同步数据源给\@Prop。 553 554 555```ts 556@Component 557struct MyComponent { 558 @Prop customCounter: number; 559 @Prop customCounter2: number = 5; 560 561 build() { 562 Column() { 563 Row() { 564 Text(`From Main: ${this.customCounter}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 }) 565 } 566 567 Row() { 568 Button('Click to change locally !') 569 .width(288) 570 .height(40) 571 .margin({ left: 30, top: 12 }) 572 .fontColor('#FFFFFF,90%') 573 .onClick(() => { 574 this.customCounter2++ 575 }) 576 } 577 578 Row() { 579 Text(`Custom Local: ${this.customCounter2}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 }) 580 } 581 } 582 } 583} 584 585@Entry 586@Component 587struct MainProgram { 588 @State mainCounter: number = 10; 589 590 build() { 591 Column() { 592 Row() { 593 Column() { 594 // customCounter必须从父组件初始化,因为MyComponent的customCounter成员变量缺少本地初始化;此处,customCounter2可以不做初始化。 595 MyComponent({ customCounter: this.mainCounter }) 596 // customCounter2也可以从父组件初始化,父组件初始化的值会覆盖子组件customCounter2的本地初始化的值 597 MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter }) 598 } 599 } 600 601 Row() { 602 Column() { 603 Button('Click to change number') 604 .width(288) 605 .height(40) 606 .margin({ left: 30, top: 12 }) 607 .fontColor('#FFFFFF,90%') 608 .onClick(() => { 609 this.mainCounter++ 610 }) 611 } 612 } 613 } 614 } 615} 616``` 617 618![Video-prop-UsageScenario-two](figures/Video-prop-UsageScenario-two.gif) 619 620### \@Prop嵌套场景 621 622在嵌套场景下,每一层都要用@Observed装饰,且每一层都要被@Prop接收,这样才能观察到嵌套场景。 623 624```ts 625// 以下是嵌套类对象的数据结构。 626@Observed 627class ClassA { 628 public title: string; 629 630 constructor(title: string) { 631 this.title = title; 632 } 633} 634 635@Observed 636class ClassB { 637 public name: string; 638 public a: ClassA; 639 640 constructor(name: string, a: ClassA) { 641 this.name = name; 642 this.a = a; 643 } 644} 645``` 646 647以下组件层次结构呈现的是@Prop嵌套场景的数据结构。 648 649```ts 650@Entry 651@Component 652struct Parent { 653 @State votes: ClassB = new ClassB('Hello', new ClassA('world')) 654 655 build() { 656 Column() { 657 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) { 658 Button('change ClassB name') 659 .width(312) 660 .height(40) 661 .margin(12) 662 .fontColor('#FFFFFF,90%') 663 .onClick(() => { 664 this.votes.name = "aaaaa" 665 }) 666 Button('change ClassA title') 667 .width(312) 668 .height(40) 669 .margin(12) 670 .fontColor('#FFFFFF,90%') 671 .onClick(() => { 672 this.votes.a.title = "wwwww" 673 }) 674 Text(this.votes.name) 675 .fontSize(16) 676 .margin(12) 677 .width(312) 678 .height(40) 679 .backgroundColor('#ededed') 680 .borderRadius(20) 681 .textAlign(TextAlign.Center) 682 .fontColor('#e6000000') 683 .onClick(() => { 684 this.votes.name = 'Bye' 685 }) 686 Text(this.votes.a.title) 687 .fontSize(16) 688 .margin(12) 689 .width(312) 690 .height(40) 691 .backgroundColor('#ededed') 692 .borderRadius(20) 693 .textAlign(TextAlign.Center) 694 .onClick(() => { 695 this.votes.a.title = "openHarmony" 696 }) 697 Child1({ vote1: this.votes.a }) 698 } 699 700 } 701 702 } 703} 704 705 706@Component 707struct Child1 { 708 @Prop vote1: ClassA = new ClassA(''); 709 710 build() { 711 Column() { 712 Text(this.vote1.title) 713 .fontSize(16) 714 .margin(12) 715 .width(312) 716 .height(40) 717 .backgroundColor('#ededed') 718 .borderRadius(20) 719 .textAlign(TextAlign.Center) 720 .onClick(() => { 721 this.vote1.title = 'Bye Bye' 722 }) 723 } 724 } 725} 726``` 727 728![Video-prop-UsageScenario-three](figures/Video-prop-UsageScenario-three.gif) 729 730## 常见问题 731 732### \@Prop装饰状态变量未初始化错误 733 734\@Prop需要被初始化,如果没有进行本地初始化的,则必须通过父组件进行初始化。如果进行了本地初始化,那么是可以不通过父组件进行初始化的。 735 736【反例】 737 738```ts 739@Observed 740class ClassA { 741 public c: number = 0; 742 743 constructor(c: number) { 744 this.c = c; 745 } 746} 747 748@Component 749struct PropChild { 750 @Prop testNum: ClassA; // 未进行本地初始化 751 752 build() { 753 Text(`PropChild testNum ${this.testNum.c}`) 754 .onClick(() => { 755 this.testNum.c += 1; 756 }) 757 } 758} 759 760@Entry 761@Component 762struct Parent { 763 @State testNum: ClassA[] = [new ClassA(1)]; 764 765 build() { 766 Column() { 767 Text(`Parent testNum ${this.testNum[0].c}`) 768 .onClick(() => { 769 this.testNum[0].c += 1; 770 }) 771 772 // @Prop本地没有初始化,也没有从父组件初始化 773 PropChild1() 774 } 775 } 776} 777``` 778 779【正例】 780 781```ts 782@Observed 783class ClassA { 784 public c: number = 0; 785 786 constructor(c: number) { 787 this.c = c; 788 } 789} 790 791@Component 792struct PropChild1 { 793 @Prop testNum: ClassA; // 未进行本地初始化 794 795 build() { 796 Text(`PropChild1 testNum ${this.testNum.c}`) 797 .onClick(() => { 798 this.testNum.c += 1; 799 }) 800 } 801} 802 803@Component 804struct PropChild2 { 805 @Prop testNum: ClassA = new ClassA(1); // 进行本地初始化 806 807 build() { 808 Text(`PropChild2 testNum ${this.testNum.c}`) 809 .onClick(() => { 810 this.testNum.c += 1; 811 }) 812 } 813} 814 815@Entry 816@Component 817struct Parent { 818 @State testNum: ClassA[] = [new ClassA(1)]; 819 820 build() { 821 Column() { 822 Text(`Parent testNum ${this.testNum[0].c}`) 823 .onClick(() => { 824 this.testNum[0].c += 1; 825 }) 826 827 // @PropChild1本地没有初始化,必须从父组件初始化 828 PropChild1({ testNum: this.testNum[0] }) 829 // @PropChild2本地进行了初始化,可以不从父组件初始化,也可以从父组件初始化 830 PropChild2() 831 PropChild2({ testNum: this.testNum[0] }) 832 } 833 } 834} 835``` 836 837<!--no_check--> 838