• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Prop Decorator: One-Way Synchronization from Parent to Child Components
2
3
4An \@Prop decorated variable can create one-way synchronization with a variable of its parent component. \@Prop decorated variables are mutable, but changes are not synchronized to the parent component.
5
6
7> **NOTE**
8>
9> Since API version 9, this decorator is supported in ArkTS widgets.
10
11
12## Overview
13
14For an \@Prop decorated variable, the value synchronization is uni-directional from the parent component to the owning component.
15
16- An @Prop variable is allowed to be modified locally, but the change does not propagate back to its parent component.
17
18- Whenever that data source changes, the @Prop decorated variable gets updated, and any locally made changes are overwritten. In other words, the value is synchronized from the parent component to the (owning) child component, but not from the other way around.
19
20
21## Rules of Use
22
23| \@Prop Decorator| Description                                      |
24| ----------- | ---------------------------------------- |
25| Decorator parameters      | None.                                       |
26| Synchronization type       | One-way: from the data source provided by the parent component to the @Prop decorated variable. For details about the scenarios of nested types, see [Observed Changes](#observed-changes).|
27| Allowed variable types  | Object, class, string, number, Boolean, enum, and array of these types.<br>**any** is not supported. A combination of simple and complex types is not supported. The **undefined** and **null** values are not allowed.<br>Date type.<br>For details about the scenarios of supported types, see [Observed Changes](#observed-changes).<br>The type must be specified.<br>**NOTE**<br>The Length, ResourceStr, and ResourceColor types are a combination of simple and complex types and therefore not supported.<br>Negative examples:<br>CompA ({ aProp: undefined })<br>CompA ({ aProp: null })<br>The type must be the same as that of the [data source](arkts-state-management-overview.md#basic-concepts). There are three cases:<br>- Synchronizing the \@Prop decorated variable from an \@State or other decorated variable. Example: [Simple Type @Prop Synced from @State in Parent Component](#simple-type-prop-synced-from-state-in-parent-component).<br>- Synchronizing the \@Prop decorated variable from the item of an \@State or other decorated array. Example: [Simple Type @Prop Synced from @State Array Item in Parent Component](#simple-type-prop-synced-from-state-array-item-in-parent-component).<br>- Synchronizing the \@Prop decorated variable from a state attribute of the Object or class type in the parent component. Example: [Class Object Type @Prop Synced from @State Class Object Attribute in Parent Component](#class-object-type-prop-synced-from-state-class-object-attribute-in-parent-component).|
28| Number of nested layers       | In component reuse scenarios, it is recommended that @Prop be nested with no more than five layers of data. If @Prop is nested with too many layers of data, garbage collection and increased memory usage caused by deep copy will follow, resulting in performance issues. To avoid such issues, use [\@ObjectLink](arkts-observed-and-objectlink.md) instead. If you do not want to synchronize the data of a child component to the parent component, consider using **aboutToReuse** in @Reusable to pass data from the parent component to the child component. For details, see [Component Reuse](arkts-state-management-best-practices.md)|
29| Initial value for the decorated variable  | Local initialization is allowed.                                |
30
31
32## Variable Transfer/Access Rules
33
34| Transfer/Access    | Description                                      |
35| --------- | ---------------------------------------- |
36| Initialization from the parent component  | Optional if local initialization is used and mandatory otherwise. An @Prop decorated variable can be initialized from a regular variable (whose change does not trigger UI refresh) or an @State, @Link, @Prop, @Provide, @Consume, @ObjectLink, @StorageLink, @StorageProp, @LocalStorageLink, or @LocalStorageProp decorated variable in its parent component.|
37| Subnode initialization | Supported; can be used to initialize a regular variable or \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
38| Access| Private, accessible only within the component.               |
39
40
41  **Figure 1** Initialization rule
42
43
44![en-us_image_0000001552972029](figures/en-us_image_0000001552972029.png)
45
46
47## Observed Changes and Behavior
48
49
50### Observed Changes
51
52\@Prop decorated variables can observe the following changes:
53
54- When the decorated variable is of the Object, class, string, number, Boolean, or enum type, its value change can be observed.
55
56  ```ts
57  // Simple type
58  @Prop count: number;
59  // The value assignment can be observed.
60  this.count = 1;
61  // Complex type
62  @Prop count: Model;
63  // The value assignment can be observed.
64  this.title = new Model('Hi');
65  ```
66
67When the decorated variable is of the Object or class type, the value changes of attributes at the first layer can be observed, that is, the attributes that **Object.keys(observedObject)** returns.
68
69```
70class ClassA {
71  public value: string;
72  constructor(value: string) {
73    this.value = value;
74  }
75}
76class Model {
77  public value: string;
78  public a: ClassA;
79  constructor(value: string, a: ClassA) {
80    this.value = value;
81    this.a = a;
82  }
83}
84
85@Prop title: Model;
86// The value changes at the first layer can be observed.
87this.title.value = 'Hi'
88// The value changes at the second layer cannot be observed.
89this.title.a.value = 'ArkUi'
90```
91
92In the scenarios of nested objects, if a class is decorated by \@Observed, the value changes of the class attribute can be observed. For the example, see [@Prop Nesting Scenario](#@prop-nesting-scenario).
93
94When the decorated variable is of the array type, the value assignment, addition, deletion, and updates of array items can be observed.
95
96```
97// When the object decorated by @State is an array
98@Prop title: string[]
99// The value assignment of the array itself can be observed.
100this.title = ['1']
101// The value assignment of array items can be observed.
102this.title[0] = '2'
103// The deletion of array items can be observed.
104this.title.pop()
105// The addition of array items can be observed.
106this.title.push('3')
107```
108
109For synchronization between \@State and \@Prop decorated variables:
110
111- The value of an \@State decorated variable in the parent component initializes an \@Prop decorated variable in the child component. The \@State decorated variable also updates the @Prop decorated variable whenever the value of the former changes.
112- Changes to the @Prop decorated variable do not affect the value of its source @State decorated variable.
113- In addition to \@State, the source can also be decorated with \@Link or \@Prop, where the mechanisms for syncing the \@Prop would be the same.
114- The source and \@Prop decorated variable must be of the same type. The \@Prop decorated variable can be of simple and class types.
115
116- When the decorated variable is of the Date type, the overall value assignment of the Date object can be observed, and the following APIs can be called to update Date attributes: **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, and **setUTCMilliseconds**.
117
118```ts
119@Component
120struct DateComponent {
121  @Prop selectedDate: Date = new Date('');
122
123  build() {
124    Column() {
125      Button('child update the new date')
126        .margin(10)
127        .onClick(() => {
128          this.selectedDate = new Date('2023-09-09')
129        })
130      Button(`child increase the year by 1`).onClick(() => {
131        this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1)
132      })
133      DatePicker({
134        start: new Date('1970-1-1'),
135        end: new Date('2100-1-1'),
136        selected: this.selectedDate
137      })
138    }
139  }
140}
141
142@Entry
143@Component
144struct ParentComponent {
145  @State parentSelectedDate: Date = new Date('2021-08-08');
146
147  build() {
148    Column() {
149      Button('parent update the new date')
150        .margin(10)
151        .onClick(() => {
152          this.parentSelectedDate = new Date('2023-07-07')
153        })
154      Button('parent increase the day by 1')
155        .margin(10)
156        .onClick(() => {
157          this.parentSelectedDate.setDate(this.parentSelectedDate.getDate() + 1)
158        })
159      DatePicker({
160        start: new Date('1970-1-1'),
161        end: new Date('2100-1-1'),
162        selected: this.parentSelectedDate
163      })
164
165      DateComponent({selectedDate:this.parentSelectedDate})
166    }
167
168  }
169}
170```
171
172### Framework Behavior
173
174To understand the value initialization and update mechanism of the \@Prop decorated variable, it is necessary to consider the parent component and the initial render and update process of the child component that owns the \@Prop decorated variable.
175
1761. Initial render:
177   1. The execution of the parent component's **build()** function creates a new instance of the child component, and the parent component provides a source for the @Prop decorated variable.
178   2. The @Prop decorated variable is initialized.
179
1802. Update:
181   1. When the @Prop decorated variable is modified locally, the change remains local and does not propagate back to its parent component.
182   2. When the data source of the parent component is updated, the \@Prop decorated variable in the child component is reset, and its local value changes are overwritten.
183
184
185## Application Scenarios
186
187
188### Simple Type @Prop Synced from @State in Parent Component
189
190
191In this example, the \@Prop decorated **count** variable in the **CountDownComponent** child component is initialized from the \@State decorated **countDownStartValue** variable in the **ParentComponent**. When **Try again** is touched, the value of the **count** variable is modified, but the change remains within the **CountDownComponent** and does not affect the **ParentComponent**.
192
193
194Updating **countDownStartValue** in the **ParentComponent** will update the value of the @Prop decorated **count**.
195
196
197
198```ts
199@Component
200struct CountDownComponent {
201  @Prop count: number = 0;
202  costOfOneAttempt: number = 1;
203
204  build() {
205    Column() {
206      if (this.count > 0) {
207        Text(`You have ${this.count} Nuggets left`)
208      } else {
209        Text('Game over!')
210      }
211      // Changes to the @Prop decorated variables are not synchronized to the parent component.
212      Button(`Try again`).onClick(() => {
213        this.count -= this.costOfOneAttempt;
214      })
215    }
216  }
217}
218
219@Entry
220@Component
221struct ParentComponent {
222  @State countDownStartValue: number = 10;
223
224  build() {
225    Column() {
226      Text(`Grant ${this.countDownStartValue} nuggets to play.`)
227      // Changes to the data source provided by the parent component are synchronized to the child component.
228      Button(`+1 - Nuggets in New Game`).onClick(() => {
229        this.countDownStartValue += 1;
230      })
231      // Updating the parent component will also update the child component.
232      Button(`-1  - Nuggets in New Game`).onClick(() => {
233        this.countDownStartValue -= 1;
234      })
235
236      CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 })
237    }
238  }
239}
240```
241
242
243In the preceding example:
244
245
2461. On initial render, when the **CountDownComponent** child component is created, its @Prop decorated **count** variable is initialized from the \@State decorated **countDownStartValue** variable in the **ParentComponent**.
247
2482. When the "+1" or "-1" button is touched, the @State decorated **countDownStartValue** of the **ParentComponent** changes. This will cause the **ParentComponent** to re-render. At the minimum, the **CountDownComponent** will be updated because of the change in the **count** variable value.
249
2503. Because of the change in the **count** variable value, the **CountDownComponent** child component will re-render. At a minimum, the **if** statement's condition (**this.counter> 0**) is evaluated, and the **\<Text>** child component inside the **if** statement would be updated.
251
2524. When **Try again** in the **CountDownComponent** child component is touched, the value of the **count** variable is modified, but the change remains within the child component and does not affect the **countDownStartValue** in the parent component.
253
2545. Updating **countDownStartValue** will overwrite the local value changes of the @Prop decorated **count** in the **CountDownComponent** child component.
255
256
257### Simple Type @Prop Synced from @State Array Item in Parent Component
258
259
260The \@State decorated array an array item in the parent component can be used as data source to initialize and update a @Prop decorated variable. In the following example, the \@State decorated array **arr** in the parent component **Index** initializes the \@Prop decorated **value** variable in the child component **Child**.
261
262
263
264```ts
265@Component
266struct Child {
267  @Prop value: number = 0;
268
269  build() {
270    Text(`${this.value}`)
271      .fontSize(50)
272      .onClick(()=>{this.value++})
273  }
274}
275
276@Entry
277@Component
278struct Index {
279  @State arr: number[] = [1,2,3];
280
281  build() {
282    Row() {
283      Column() {
284        Child({value: this.arr[0]})
285        Child({value: this.arr[1]})
286        Child({value: this.arr[2]})
287
288        Divider().height(5)
289
290        ForEach(this.arr,
291          (item: void) => {
292            Child({value: item})
293          },
294          (item: string) => item.toString()
295        )
296        Text('replace entire arr')
297        .fontSize(50)
298        .onClick(()=>{
299          // Both arrays contain item "3".
300          this.arr = this.arr[0] == 1 ? [3,4,5] : [1,2,3];
301        })
302      }
303    }
304  }
305}
306```
307
308
309Initial render creates six instances of the **Child** component. Each \@Prop decorated variable is initialized with a copy of an array item. The **onclick** event handler of the **Child** component changes the local variable value.
310
311
312Assume that we clicked so many times that all local values be '7'.
313
314
315
316```
3177
3187
3197
320----
3217
3227
3237
324```
325
326
327After **replace entire arr** is clicked, the following information is displayed:
328
329
330
331```
3323
3334
3345
335----
3367
3374
3385
339```
340
341
342- Changes made in the **Child** component are not synchronized to the parent component **Index**. Therefore, even if the values of the six instances of the **Child** component are 7, the value of **this.arr** in the **Index** component is still **[1,2,3]**.
343
344- After **replace entire arr** is clicked, if **this.arr[0] == 1** is true, **this.arr** is set to **[3, 4, 5]**.
345
346- Because **this.arr[0]** has been changed, the **Child({value: this.arr[0]})** component synchronizes the update of **this.arr[0]** to the instance's \@Prop decorated variable. The same happens for **Child({value: this.arr[1]})** and **Child({value: this.arr[2]})**.
347
348
349- The change of **this.arr** causes **ForEach** to update: The array item with the ID **3** is retained in this update, array items with IDs **1** and **2** are deleted, and array items with IDs **4** and **5** are added. The array before and after the update is **[1, 2, 3]** and **[3, 4, 5]**, respectively. This implies that the **Child** instance generated for item **3** will be moved to the first place, but not updated. In this case, the component value corresponding to **3** is **7**, and the final render result of **ForEach** is **7**, **4**, and **5**.
350
351
352### Class Object Type @Prop Synced from @State Class Object Attribute in Parent Component
353
354In a library with one book and two users, each user can mark the book as read, and the marking does not affect the other user reader. Technically speaking, local changes to the \@Prop decorated **book** object do not sync back to the @State decorated **book** in the **Library** component.
355
356In this example, the \@Observed decorator can be applied to the **book** class, but it is not mandatory. It is only needed for nested structures. This will be further explained in [Class Type @Prop Synced from @State Array Item in Parent Component](#class-type-prop-synced-from-state-array-item-in-parent-component).
357
358
359```ts
360class Book {
361  public title: string;
362  public pages: number;
363  public readIt: boolean = false;
364
365  constructor(title: string, pages: number) {
366    this.title = title;
367    this.pages = pages;
368  }
369}
370
371@Component
372struct ReaderComp {
373  @Prop book: Book = new Book("", 0);
374
375  build() {
376    Row() {
377      Text(this.book.title)
378      Text(`...has${this.book.pages} pages!`)
379      Text(`...${this.book.readIt ? "I have read" : 'I have not read it'}`)
380        .onClick(() => this.book.readIt = true)
381    }
382  }
383}
384
385@Entry
386@Component
387struct Library {
388  @State book: Book = new Book('100 secrets of C++', 765);
389
390  build() {
391    Column() {
392      ReaderComp({ book: this.book })
393      ReaderComp({ book: this.book })
394    }
395  }
396}
397```
398
399### Class Type @Prop Synced from @State Array Item in Parent Component
400
401In the following example, an attribute of the **Book** object in the \@State decorated **allBooks** array is changed, but the system does not respond when **Mark read for everyone** is clicked. This is because the attribute is a nested attribute of the second layer, and the \@State decorator can observe only the attribute of the first layer. Therefore, the framework does not update **ReaderComp**.
402
403```ts
404let nextId: number = 1;
405
406// @Observed
407class Book {
408  public id: number;
409  public title: string;
410  public pages: number;
411  public readIt: boolean = false;
412
413  constructor(title: string, pages: number) {
414    this.id = nextId++;
415    this.title = title;
416    this.pages = pages;
417  }
418}
419
420@Component
421struct ReaderComp {
422  @Prop book: Book = new Book();
423
424  build() {
425    Row() {
426      Text(this.book.title)
427      Text(`...has${this.book.pages} pages!`)
428      Text(`...${this.book.readIt ? "I have read" : 'I have not read it'}`)
429        .onClick(() => this.book.readIt = true)
430    }
431  }
432}
433
434@Entry
435@Component
436struct Library {
437  @State allBooks: Book[] = [new Book("100 secrets of C++", 765), new Book("Effective C++", 651), new Book("The C++ programming language", 1765)];
438
439  build() {
440    Column() {
441      Text('library`s all time favorite')
442      ReaderComp({ book: this.allBooks[2] })
443      Divider()
444      Text('Books on loaan to a reader')
445      ForEach(this.allBooks, (book: void) => {
446        ReaderComp({ book: book })
447      },
448        (book: number): number => book.id)
449      Button('Add new')
450        .onClick(() => {
451          this.allBooks.push(new Book("The C++ Standard Library", 512));
452        })
453      Button('Remove first book')
454        .onClick(() => {
455          this.allBooks.shift();
456        })
457      Button("Mark read for everyone")
458        .onClick(() => {
459          this.allBooks.forEach((book) => book.readIt = true)
460        })
461    }
462  }
463}
464```
465
466 To observe the attribute of the **Book** object, you must use \@Observed to decorate the **Book** class. Note that the \@Prop decorated state variable in the child component is one-way synchronized from the data source of the parent component. This means that, the changes of the \@Prop decorated **book** in **ReaderComp** is not synchronized to the parent **library** component. The parent component triggers UI re-rendering only when the value is updated (compared with the last state).
467
468```ts
469@Observed
470class Book {
471  public id: number;
472  public title: string;
473  public pages: number;
474  public readIt: boolean = false;
475
476  constructor(title: string, pages: number) {
477    this.id = nextId++;
478    this.title = title;
479    this.pages = pages;
480  }
481}
482```
483
484All instances of the \@Observed decorated class are wrapped with an opaque proxy object. This proxy can detect all attribute changes inside the wrapped object. If any attribute change happens, the proxy notifies the \@Prop, and the \@Prop value will be updated.
485
486### Simple Type @Prop with Local Initialization and No Sync from Parent Component
487
488To enable an \@Component decorated component to be reusable, \@Prop allows for optional local initialization. This makes the synchronization with a variable in the parent component a choice, rather than mandatory. Providing a data source in the parent component is optional only when local initialization is provided for the \@Prop decorated variable.
489
490The following example includes two @Prop decorated variables in child component.
491
492- The @Prop decorated variable **customCounter** has no local initialization, and therefore it requires a synchronization source in its parent component. When the source value changes, the @Prop decorated variable is updated.
493
494- The @Prop decorated variable **customCounter2** has local initialization. In this case, specifying a synchronization source in the parent component is allowed but not mandatory.
495
496
497```ts
498@Component
499struct MyComponent {
500  @Prop customCounter: number = 0;
501  @Prop customCounter2: number = 5;
502
503  build() {
504    Column() {
505      Row() {
506        Text(`From Main: ${this.customCounter}`).width(90).height(40).fontColor('#FF0010')
507      }
508
509      Row() {
510        Button('Click to change locally !').width(180).height(60).margin({ top: 10 })
511          .onClick(() => {
512            this.customCounter2++
513          })
514      }.height(100).width(180)
515
516      Row() {
517        Text(`Custom Local: ${this.customCounter2}`).width(90).height(40).fontColor('#FF0010')
518      }
519    }
520  }
521}
522
523@Entry
524@Component
525struct MainProgram {
526  @State mainCounter: number = 10;
527
528  build() {
529    Column() {
530      Row() {
531        Column() {
532          Button('Click to change number').width(480).height(60).margin({ top: 10, bottom: 10 })
533            .onClick(() => {
534              this.mainCounter++
535            })
536        }
537      }
538
539      Row() {
540        Column() {
541          // customCounter must be initialized from the parent component due to lack of local initialization. Here, customCounter2 does not need to be initialized.
542          MyComponent({ customCounter: this.mainCounter })
543          // customCounter2 of the child component can also be initialized from the parent component. The value from the parent component overwrites the locally assigned value of customCounter2 during initialization.
544          MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter })
545        }
546      }
547    }
548  }
549}
550```
551
552### \@Prop Nesting Scenario
553
554In nesting scenario, each layer must be decorated with @Observed, and each layer must be received by @Prop. In this way, the nested scenario can be observed.
555
556```ts
557// The following is the data structure of a nested class object.
558@Observed
559class ClassA {
560  public title: string;
561
562  constructor(title: string) {
563    this.title = title;
564  }
565}
566
567@Observed
568class ClassB {
569  public name: string;
570  public a: ClassA;
571
572  constructor(name: string, a: ClassA) {
573    this.name = name;
574    this.a = a;
575  }
576}
577```
578
579The following component hierarchy presents a data structure of nested @Prop.
580
581```ts
582@Entry
583@Component
584struct Parent {
585  @State votes: ClassB = new ClassB('Hello', new ClassA('world'))
586
587  build() {
588    Column() {
589      Button('change')
590        .onClick(() => {
591          this.votes.name = "aaaaa"
592          this.votes.a.title = "wwwww"
593        })
594      Child({ vote: this.votes })
595    }
596
597  }
598}
599
600@Component
601struct Child {
602  @Prop vote: ClassB = new ClassB('', new ClassA(''));
603  build() {
604    Column() {
605
606      Text(this.vote.name).fontSize(36).fontColor(Color.Red).margin(50)
607        .onClick(() => {
608          this.vote.name = 'Bye'
609        })
610      Text(this.vote.a.title).fontSize(36).fontColor(Color.Blue)
611        .onClick(() => {
612          this.vote.a.title = "openHarmony"
613        })
614      Child1({vote1:this.vote.a})
615
616    }
617  }
618}
619
620@Component
621struct Child1 {
622  @Prop vote1: ClassA = new ClassA('');
623  build() {
624    Column() {
625      Text(this.vote1.title).fontSize(36).fontColor(Color.Red).margin(50)
626        .onClick(() => {
627          this.vote1.title = 'Bye Bye'
628        })
629    }
630  }
631}
632```
633
634<!--no_check-->
635