• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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&nbsp;({&nbsp;aProp:&nbsp;undefined&nbsp;})<br/>CompA&nbsp;({&nbsp;aProp:&nbsp;null&nbsp;})<br/>\@Prop和[数据源](arkts-state-management-overview.md#基本概念)类型需要相同,有以下三种情况(数据源以\@State为例):<br/>-&nbsp;\@Prop装饰的变量和父组件状态变量类型相同,即\@Prop&nbsp;:&nbsp;S和\@State&nbsp;:&nbsp;S,示例请参考[父组件@State到子组件@Prop简单数据类型同步](#父组件state到子组件prop简单数据类型同步)。<br/>-&nbsp;当父组件的状态变量为数组时,\@Prop装饰的变量和父组件状态变量的数组项类型相同,即\@Prop&nbsp;:&nbsp;S和\@State&nbsp;:&nbsp;Array&lt;S&gt;,示例请参考[父组件@State数组中的项到子组件@Prop简单数据类型同步](#父组件state数组项到子组件prop简单数据类型同步);<br/>-&nbsp;当父组件状态变量为Object或者class时,\@Prop装饰的变量和父组件状态变量的属性类型相同,即\@Prop&nbsp;:&nbsp;S和\@State&nbsp;:&nbsp;{&nbsp;propA:&nbsp;S&nbsp;},示例请参考[从父组件中的@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 &gt; 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