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装饰的变量都会自动更新。如果子组件已经在本地修改了\@Prop装饰的相关变量值,而在父组件中对应的\@State装饰的变量被修改后,子组件本地修改的\@Prop装饰的相关变量值将被覆盖。 19 20 21## 装饰器使用规则说明 22 23| \@Prop变量装饰器 | 说明 | 24| ----------- | ---------------------------------------- | 25| 装饰器参数 | 无 | 26| 同步类型 | 单向同步:对父组件状态变量值的修改,将同步给子组件\@Prop装饰的变量,子组件\@Prop变量的修改不会同步到父组件的状态变量上 | 27| 允许装饰的变量类型 | string、number、boolean、enum类型。<br/>不支持any,不允许使用undefined和null。<br/>必须指定类型。<br/>在父组件中,传递给\@Prop装饰的值不能为undefined或者null,反例如下所示。<br/>CompA ({ aProp: undefined })<br/>CompA ({ aProp: null })<br/>\@Prop和[数据源](arkts-state-management-overview.md#基本概念)类型需要相同,有以下三种情况(数据源以\@State为例):<br/>- \@Prop装饰的变量和父组件状态变量类型相同,即\@Prop : S和\@State : S,示例请参考[父组件@State到子组件@Prop简单数据类型同步](#父组件state到子组件prop简单数据类型同步)。<br/>- 当父组件的状态变量为数组时,\@Prop装饰的变量和父组件状态变量的数组项类型相同,即\@Prop : S和\@State : Array<S>,示例请参考[父组件@State数组中的项到子组件@Prop简单数据类型同步](#父组件state数组项到子组件prop简单数据类型同步);<br/>- 当父组件状态变量为Object或者class时,\@Prop装饰的变量和父组件状态变量的属性类型相同,即\@Prop : S和\@State : { propA: S },示例请参考[从父组件中的@State类对象属性到@Prop简单类型的同步](#从父组件中的state类对象属性到prop简单类型的同步)。 | 28| 被装饰变量的初始值 | 允许本地初始化。 | 29 30 31## 变量的传递/访问规则说明 32 33| 传递/访问 | 说明 | 34| --------- | ---------------------------------------- | 35| 从父组件初始化 | 如果本地有初始化,则是可选的。没有的话,则必选,支持父组件中的常规变量、\@State、\@Link、\@Prop、\@Provide、\@Consume、\@ObjectLink、\@StorageLink、\@StorageProp、\@LocalStorageLink和\@LocalStorageProp去初始化子组件中的\@Prop变量。 | 36| 用于初始化子组件 | \@Prop支持去初始化子组件中的常规变量、\@State、\@Link、\@Prop、\@Provide。 | 37| 是否支持组件外访问 | \@Prop装饰的变量是私有的,只能在组件内访问。 | 38 39 40 **图1** 初始化规则图示 41 42 43![zh-cn_image_0000001552972029](figures/zh-cn_image_0000001552972029.png) 44 45 46## 观察变化和行为表现 47 48 49### 观察变化 50 51\@Prop装饰的数据可以观察到以下变化。 52 53- 当装饰的类型是允许的类型,即string、number、boolean、enum类型都可以观察到的赋值变化; 54 55 ```ts 56 // 简单类型 57 @Prop count: number; 58 // 赋值的变化可以被观察到 59 this.count = 1; 60 ``` 61 62对于\@State和\@Prop的同步场景: 63 64- 使用父组件中\@State变量的值初始化子组件中的\@Prop变量。当\@State变量变化时,该变量值也会同步更新至\@Prop变量。 65 66- \@Prop装饰的变量的修改不会影响其数据源\@State装饰变量的值。 67 68- 除了\@State,数据源也可以用\@Link或\@Prop装饰,对\@Prop的同步机制是相同的。 69 70- 数据源和\@Prop变量的类型需要相同。 71 72 73### 框架行为 74 75要理解\@Prop变量值初始化和更新机制,有必要了解父组件和拥有\@Prop变量的子组件初始渲染和更新流程。 76 771. 初始渲染: 78 1. 执行父组件的build()函数将创建子组件的新实例,将数据源传递给子组件; 79 2. 初始化子组件\@Prop装饰的变量。 80 812. 更新: 82 1. 子组件\@Prop更新时,更新仅停留在当前子组件,不会同步回父组件; 83 2. 当父组件的数据源更新时,子组件的\@Prop装饰的变量将被来自父组件的数据源重置,所有\@Prop装饰的本地的修改将被父组件的更新覆盖。 84 85 86## 使用场景 87 88 89### 父组件\@State到子组件\@Prop简单数据类型同步 90 91 92以下示例是\@State到子组件\@Prop简单数据同步,父组件ParentComponent的状态变量countDownStartValue初始化子组件CountDownComponent中\@Prop装饰的count,点击“Try again”,count的修改仅保留在CountDownComponent 不会同步给父组件CountDownComponent。 93 94 95ParentComponent的状态变量countDownStartValue的变化将重置CountDownComponent的count。 96 97 98 99```ts 100@Component 101struct CountDownComponent { 102 @Prop count: number; 103 costOfOneAttempt: number = 1; 104 105 build() { 106 Column() { 107 if (this.count > 0) { 108 Text(`You have ${this.count} Nuggets left`) 109 } else { 110 Text('Game over!') 111 } 112 // @Prop装饰的变量不会同步给父组件 113 Button(`Try again`).onClick(() => { 114 this.count -= this.costOfOneAttempt; 115 }) 116 } 117 } 118} 119 120@Entry 121@Component 122struct ParentComponent { 123 @State countDownStartValue: number = 10; 124 125 build() { 126 Column() { 127 Text(`Grant ${this.countDownStartValue} nuggets to play.`) 128 // 父组件的数据源的修改会同步给子组件 129 Button(`+1 - Nuggets in New Game`).onClick(() => { 130 this.countDownStartValue += 1; 131 }) 132 // 父组件的修改会同步给子组件 133 Button(`-1 - Nuggets in New Game`).onClick(() => { 134 this.countDownStartValue -= 1; 135 }) 136 137 CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 }) 138 } 139 } 140} 141``` 142 143 144在上面的示例中: 145 146 1471. CountDownComponent子组件首次创建时其\@Prop装饰的count变量将从父组件\@State装饰的countDownStartValue变量初始化; 148 1492. 按“+1”或“-1”按钮时,父组件的\@State装饰的countDownStartValue值会变化,这将触发父组件重新渲染,在父组件重新渲染过程中会刷新使用countDownStartValue状态变量的UI组件并单向同步更新CountDownComponent子组件中的count值; 150 1513. 更新count状态变量值也会触发CountDownComponent的重新渲染,在重新渲染过程中,评估使用count状态变量的if语句条件(this.count > 0),并执行true分支中的使用count状态变量的UI组件相关描述来更新Text组件的UI显示; 152 1534. 当按下子组件CountDownComponent的“Try again”按钮时,其\@Prop变量count将被更改,但是count值的更改不会影响父组件的countDownStartValue值; 154 1555. 父组件的countDownStartValue值会变化时,父组件的修改将覆盖掉子组件CountDownComponent中count本地的修改。 156 157 158### 父组件\@State数组项到子组件\@Prop简单数据类型同步 159 160 161父组件中\@State如果装饰的数组,其数组项也可以初始化\@Prop。以下示例中父组件Index中\@State装饰的数组arr,将其数组项初始化子组件Child中\@Prop装饰的value。 162 163 164 165```ts 166@Component 167struct Child { 168 @Prop value: number; 169 170 build() { 171 Text(`${this.value}`) 172 .fontSize(50) 173 .onClick(()=>{this.value++}) 174 } 175} 176 177@Entry 178@Component 179struct Index { 180 @State arr: number[] = [1,2,3]; 181 182 build() { 183 Row() { 184 Column() { 185 Child({value: this.arr[0]}) 186 Child({value: this.arr[1]}) 187 Child({value: this.arr[2]}) 188 189 Divider().height(5) 190 191 ForEach(this.arr, 192 item => { 193 Child({value: item}) 194 }, 195 item => item.toString() 196 ) 197 Text('replace entire arr') 198 .fontSize(50) 199 .onClick(()=>{ 200 // 两个数组都包含项“3”。 201 this.arr = this.arr[0] == 1 ? [3,4,5] : [1,2,3]; 202 }) 203 } 204 } 205 } 206} 207``` 208 209 210初始渲染创建6个子组件实例,每个\@Prop装饰的变量初始化都在本地拷贝了一份数组项。子组件onclick事件处理程序会更改局部变量值。 211 212 213假设我们点击了多次,所有变量的本地取值都是“7”。 214 215 216 217``` 2187 2197 2207 221---- 2227 2237 2247 225``` 226 227 228单击replace entire arr后,屏幕将显示以下信息。 229 230 231 232``` 2333 2344 2355 236---- 2377 2384 2395 240``` 241 242 243- 在子组件Child中做的所有的修改都不会同步回父组件Index组件,所以即使6个组件显示都为7,但在父组件Index中,this.arr保存的值依旧是[1,2,3]。 244 245- 点击replace entire arr,this.arr[0] == 1成立,将this.arr赋值为[3, 4, 5]; 246 247- 因为this.arr[0]已更改,Child({value: this.arr[0]})组件将this.arr[0]更新同步到实例\@Prop装饰的变量。Child({value: this.arr[1]})和Child({value: this.arr[2]})的情况也类似。 248 249 250- 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”。 251 252 253### 从父组件中的\@State类对象属性到\@Prop简单类型的同步 254 255如果图书馆有一本图书和两位用户,每位用户都可以将图书标记为已读,此标记行为不会影响其它读者用户。从代码角度讲,对\@Prop图书对象的本地更改不会同步给图书馆组件中的\@State图书对象。 256 257 258```ts 259class Book { 260 public title: string; 261 public pages: number; 262 public readIt: boolean = false; 263 264 constructor(title: string, pages: number) { 265 this.title = title; 266 this.pages = pages; 267 } 268} 269 270@Component 271struct ReaderComp { 272 @Prop title: string; 273 @Prop readIt: boolean; 274 275 build() { 276 Row() { 277 Text(this.title) 278 Text(`... ${this.readIt ? 'I have read' : 'I have not read it'}`) 279 .onClick(() => this.readIt = true) 280 } 281 } 282} 283 284@Entry 285@Component 286struct Library { 287 @State book: Book = new Book('100 secrets of C++', 765); 288 289 build() { 290 Column() { 291 ReaderComp({ title: this.book.title, readIt: this.book.readIt }) 292 ReaderComp({ title: this.book.title, readIt: this.book.readIt }) 293 } 294 } 295} 296``` 297 298 299### \@Prop本地初始化不和父组件同步 300 301为了支持\@Component装饰的组件复用场景,\@Prop支持本地初始化,这样可以让\@Prop是否与父组件建立同步关系变得可选。当且仅当\@Prop有本地初始化时,从父组件向子组件传递\@Prop的数据源才是可选的。 302 303下面的示例中,子组件包含两个\@Prop变量: 304 305- \@Prop customCounter没有本地初始化,所以需要父组件提供数据源去初始化\@Prop,并当父组件的数据源变化时,\@Prop也将被更新; 306 307- \@Prop customCounter2有本地初始化,在这种情况下,\@Prop依旧允许但非强制父组件同步数据源给\@Prop。 308 309 310```ts 311@Component 312struct MyComponent { 313 @Prop customCounter: number; 314 @Prop customCounter2: number = 5; 315 316 build() { 317 Column() { 318 Row() { 319 Text(`From Main: ${this.customCounter}`).width(90).height(40).fontColor('#FF0010') 320 } 321 322 Row() { 323 Button('Click to change locally !').width(480).height(60).margin({ top: 10 }) 324 .onClick(() => { 325 this.customCounter2++ 326 }) 327 }.height(100).width(480) 328 329 Row() { 330 Text(`Custom Local: ${this.customCounter2}`).width(90).height(40).fontColor('#FF0010') 331 } 332 } 333 } 334} 335 336@Entry 337@Component 338struct MainProgram { 339 @State mainCounter: number = 10; 340 341 build() { 342 Column() { 343 Row() { 344 Column() { 345 Button('Click to change number').width(480).height(60).margin({ top: 10, bottom: 10 }) 346 .onClick(() => { 347 this.mainCounter++ 348 }) 349 } 350 } 351 352 Row() { 353 Column() 354 // customCounter必须从父组件初始化,因为MyComponent的customCounter成员变量缺少本地初始化;此处,customCounter2可以不做初始化。 355 MyComponent({ customCounter: this.mainCounter }) 356 // customCounter2也可以从父组件初始化,父组件初始化的值会覆盖子组件customCounter2的本地初始化的值 357 MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter }) 358 }.width('40%') 359 } 360 } 361} 362``` 363