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