• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Best Practices for State Management
2
3
4This guide outlines best practices for state management in ArkUI applications. Read on to discover the common pitfalls in state management and how to avoid them, with carefully selected examples of recommended and not-recommended practices.
5
6
7## Basic Example
8
9The following example describes the initialization rules of the \@Prop, \@Link, and \@ObjectLink decorators. Before we dive in, a basic knowledge of these decorators is helpful.
10
11- \@Prop: An \@Prop decorated variable can be initialized from an \@State decorated variable of the parent component, a @State decorated attribute of the Object or class type in the parent component, or the item of an @State decorated array.
12
13- \@ObjectLink: The initialization rule is the same as that of \@Prop, but an \@ObjectLink decorated variable must be initialized from an instance of an \@Observed decorated class.
14
15- \@Link: The value type must be the same as that of \@State or any other data source.
16
17
18### Not Recommended
19
20
21
22```ts
23@Observed
24class ClassA {
25  public c: number = 0;
26
27  constructor(c: number) {
28    this.c = c;
29  }
30}
31
32@Component
33struct LinkChild {
34  @Link testNum: number;
35
36  build() {
37    Text(`LinkChild testNum ${this.testNum}`)
38  }
39}
40
41
42@Component
43struct PropChild2 {
44  @Prop testNum: ClassA = new ClassA(0);
45
46  build() {
47    Text(`PropChild2 testNum ${this.testNum.c}`)
48      .onClick(() => {
49        this.testNum.c += 1;
50      })
51  }
52}
53
54@Component
55struct PropChild3 {
56  @Prop testNum: ClassA = new ClassA(0);
57
58  build() {
59    Text(`PropChild3 testNum ${this.testNum.c}`)
60  }
61}
62
63@Component
64struct ObjectLinkChild {
65  @ObjectLink testNum: ClassA;
66
67  build() {
68    Text(`ObjectLinkChild testNum ${this.testNum.c}`)
69      .onClick(() => {
70        // Issue 4: ObjectLink cannot be assigned a value.
71        this.testNum = new ClassA(47);
72      })
73  }
74}
75
76@Entry
77@Component
78struct Parent {
79  @State testNum: ClassA[] = [new ClassA(1)];
80
81  build() {
82    Column() {
83      Text(`Parent testNum ${this.testNum.c}`)
84        .onClick(() => {
85          this.testNum[0].c += 1;
86        })
87      // Issue 1: The type of the @Link decorated variable is not the same as that of the data source @State.
88      LinkChild({ testNum: this.testNum.c })
89
90      // Issue 2: The @Prop decorated variable is not initialized locally or initialized from the parent component.
91      PropChild2()
92
93      // Issue 3: PropChild3 does not change the value of @Prop testNum: ClassA. Therefore, @ObjectLink is a better choice here.
94      PropChild3({ testNum: this.testNum[0] })
95
96      ObjectLinkChild({ testNum: this.testNum[0] })
97    }
98  }
99}
100```
101
102
103The preceding example contains several errors:
104
105
1061. \@Component LinkChild: The type of **\@Link testNum: number** and the initialization from the parent component **LinkChild ({testNum:this.testNum.c})** are incorrect. The data source of \@Link must be a decorated state variable. The \@Link decorated variables must be of the same type as the data source, for example, \@Link: T and \@State: T. Therefore, the value should be changed to **\@Link testNum: ClassA**, and the initialization from the parent component should be **LinkChild({testNum: $testNum})**.
107
1082. \@Component PropChild2: An \@Prop decorated variable can be initialized locally or from the parent component, but it must be initialized. **\@Prop testNum: ClassA** is not initialized locally, and therefore it must be initialized from the parent component: **PropChild1({testNum: this.testNum})**.
109
1103. \@Component PropChild3: The **\@Prop testNum: ClassA** value is not changed. Therefore, \@ObjectLink is a better choice here, because \@Prop involves a deep copy, which can result in an increase in overhead.
111
1124. Clicking ObjectLinkChild to assign a value to the \@ObjectLink decorated variable: **this.testNum = new ClassA(47);** is not allowed. For \@ObjectLink that implements two-way data synchronization, assigning a value is equivalent to updating the array item or class attribute in the parent component, which is not supported in TypeScript/JavaScript and will result in a runtime error.
113
1145. In a non-nested scenario, for example, where the variable declared in the parent is **\@State testNum: ClassA = new ClassA(1)**, **Class A** does not need to be decorated by \@Observed, since \@State is able to observe changes at the first layer.
115
116
117### Recommended
118
119
120
121```ts
122@Observed
123class ClassA {
124  public c: number = 0;
125
126  constructor(c: number) {
127    this.c = c;
128  }
129}
130
131@Component
132struct LinkChild {
133 @Link testNum: ClassA;
134
135 build() {
136   Text(`LinkChild testNum ${this.testNum?.c}`)
137 }
138}
139
140@Component
141struct PropChild1 {
142 @Prop testNum: ClassA = new ClassA(1);
143
144 build() {
145   Text(`PropChild1 testNum ${this.testNum?.c}`)
146     .onClick(() => {
147       this.testNum = new ClassA(48);
148     })
149 }
150}
151
152@Component
153struct ObjectLinkChild {
154  @ObjectLink testNum: ClassA;
155
156  build() {
157    Text(`ObjectLinkChild testNum ${this.testNum.c}`)
158      // The @ObjectLink decorated variable can have the attribute updated.
159      .onClick(() => {
160        this.testNum.c += 1;
161      })
162  }
163}
164
165@Entry
166@Component
167struct Parent {
168  @State testNum: ClassA[] = [new ClassA(1)];
169
170  build() {
171    Column() {
172      Text(`Parent testNum ${this.testNum.c}`)
173        .onClick(() => {
174          this.testNum[0].c += 1;
175        })
176      // The type of the @Link decorated variable must be the same as that of the data source @State.
177      LinkChild({ testNum: this.testNum[0] })
178
179      // @Prop is initialized locally and therefore does not need to be initialized from the parent component.
180      PropChild1()
181
182      // When a child component does not need to be changed locally, @ObjectLink is preferred over @Prop, whose deep copy can result in an increase in overhead.
183      ObjectLinkChild({ testNum: this.testNum[0] })
184    }
185  }
186}
187```
188
189
190
191## UI Not Updating on Attribute Changes in Simple Nested Objects
192
193If you find your application UI not updating after an attribute in a nested object is changed, you may want to check the decorators in use.
194
195Each decorator has its scope of observable changes, and only those observed changes can cause the UI to update. The \@Observed decorator can observe the attribute changes of nested objects, while other decorators can observe only the changes at the second layer.
196
197
198### Not Recommended
199
200In the following example, some UI components are not updated.
201
202
203```ts
204class ClassA {
205  a: number;
206
207  constructor(a: number) {
208    this.a = a;
209  }
210
211  getA(): number {
212    return this.a;
213  }
214
215  setA(a: number): void {
216    this.a = a;
217  }
218}
219
220class ClassC {
221  c: number;
222
223  constructor(c: number) {
224    this.c = c;
225  }
226
227  getC(): number {
228    return this.c;
229  }
230
231  setC(c: number): void {
232    this.c = c;
233  }
234}
235
236class ClassB extends ClassA {
237  b: number = 47;
238  c: ClassC;
239
240  constructor(a: number, b: number, c: number) {
241    super(a);
242    this.b = b;
243    this.c = new ClassC(c);
244  }
245
246  getB(): number {
247    return this.b;
248  }
249
250  setB(b: number): void {
251    this.b = b;
252  }
253
254  getC(): number {
255    return this.c.getC();
256  }
257
258  setC(c: number): void {
259    return this.c.setC(c);
260  }
261}
262
263
264@Entry
265@Component
266struct MyView {
267  @State b: ClassB = new ClassB(10, 20, 30);
268
269  build() {
270    Column({ space: 10 }) {
271      Text(`a: ${this.b.a}`)
272      Button("Change ClassA.a")
273        .onClick(() => {
274          this.b.a += 1;
275        })
276
277      Text(`b: ${this.b.b}`)
278      Button("Change ClassB.b")
279        .onClick(() => {
280          this.b.b += 1;
281        })
282
283      Text(`c: ${this.b.c.c}`)
284      Button("Change ClassB.ClassC.c")
285        .onClick(() => {
286          // The <Text> component is not updated when clicked.
287          this.b.c.c += 1;
288        })
289    }
290  }
291}
292```
293
294- The UI is not updated when the last **\<Text>** component Text('c: ${this.b.c.c}') is clicked. This is because, **\@State b: ClassB** can observe only the changes of the **this.b** attribute, such as **this.b.a**, **this.b.b**, and **this.b.c**, but cannot observe the attributes nested in the attribute, that is, **this.b.c.c** (attribute **c** is an attribute of the **ClassC** object nested in **b**).
295
296- To observe the attributes of nested object **ClassC**, you need to make the following changes:
297  - Construct a child component for separate rendering of the **ClassC** instance. Then, in this child component, you can use \@ObjectLink or \@Prop to decorate **c : ClassC**. In general cases, use \@ObjectLink, unless local changes to the **ClassC** object are required.
298  - The nested **ClassC** object must be decorated by \@Observed. When a **ClassC** object is created in **ClassB** (**ClassB(10, 20, 30)** in this example), it is wrapped in the ES6 proxy. When the **ClassC** attribute changes (this.b.c.c += 1), the \@ObjectLink decorated variable is notified of the change.
299
300
301### Recommended
302
303The following example uses \@Observed/\@ObjectLink to observe property changes for nested objects.
304
305
306```ts
307class ClassA {
308  a: number;
309  constructor(a: number) {
310    this.a = a;
311  }
312  getA() : number {
313    return this.a; }
314  setA( a: number ) : void {
315    this.a = a; }
316}
317
318@Observed
319class ClassC {
320  c: number;
321  constructor(c: number) {
322    this.c = c;
323  }
324  getC() : number {
325    return this.c; }
326  setC(c : number) : void {
327    this.c = c; }
328}
329
330class ClassB extends ClassA {
331  b: number = 47;
332  c: ClassC;
333
334  constructor(a: number, b: number, c: number) {
335    super(a);
336    this.b = b;
337    this.c = new ClassC(c);
338  }
339
340  getB() : number {
341    return this.b; }
342  setB(b : number) : void {
343    this.b = b; }
344  getC() : number {
345    return this.c.getC(); }
346  setC(c : number) : void {
347    return this.c.setC(c); }
348}
349
350@Component
351struct ViewClassC {
352
353    @ObjectLink c : ClassC;
354    build() {
355        Column({space:10}) {
356            Text(`c: ${this.c.getC()}`)
357            Button("Change C")
358                .onClick(() => {
359                    this.c.setC(this.c.getC()+1);
360                })
361        }
362    }
363}
364
365@Entry
366@Component
367struct MyView {
368    @State b : ClassB = new ClassB(10, 20, 30);
369
370    build() {
371        Column({space:10}) {
372            Text(`a: ${this.b.a}`)
373             Button("Change ClassA.a")
374            .onClick(() => {
375                this.b.a +=1;
376            })
377
378            Text(`b: ${this.b.b}`)
379            Button("Change ClassB.b")
380            .onClick(() => {
381                this.b.b += 1;
382            })
383
384            ViewClassC({c: this.b.c})   // Equivalent to Text(`c: ${this.b.c.c}`)
385            Button("Change ClassB.ClassC.c")
386            .onClick(() => {
387                this.b.c.c += 1;
388            })
389        }
390     }
391}
392```
393
394
395
396## UI Not Updating on Attribute Changes in Complex Nested Objects
397
398
399### Not Recommended
400
401The following example creates a child component with an \@ObjectLink decorated variable to render **ParentCounter** with nested attributes. **SubCounter** nested in **ParentCounter** is decorated with \@Observed.
402
403
404```ts
405let nextId = 1;
406@Observed
407class SubCounter {
408  counter: number;
409  constructor(c: number) {
410    this.counter = c;
411  }
412}
413@Observed
414class ParentCounter {
415  id: number;
416  counter: number;
417  subCounter: SubCounter;
418  incrCounter() {
419    this.counter++;
420  }
421  incrSubCounter(c: number) {
422    this.subCounter.counter += c;
423  }
424  setSubCounter(c: number): void {
425    this.subCounter.counter = c;
426  }
427  constructor(c: number) {
428    this.id = nextId++;
429    this.counter = c;
430    this.subCounter = new SubCounter(c);
431  }
432}
433@Component
434struct CounterComp {
435  @ObjectLink value: ParentCounter;
436  build() {
437    Column({ space: 10 }) {
438      Text(`${this.value.counter}`)
439        .fontSize(25)
440        .onClick(() => {
441          this.value.incrCounter();
442        })
443      Text(`${this.value.subCounter.counter}`)
444        .onClick(() => {
445          this.value.incrSubCounter(1);
446        })
447      Divider().height(2)
448    }
449  }
450}
451@Entry
452@Component
453struct ParentComp {
454  @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
455  build() {
456    Row() {
457      Column() {
458        CounterComp({ value: this.counter[0] })
459        CounterComp({ value: this.counter[1] })
460        CounterComp({ value: this.counter[2] })
461        Divider().height(5)
462        ForEach(this.counter,
463          (item: ParentCounter) => {
464            CounterComp({ value: item })
465          },
466          (item: ParentCounter) => item.id.toString()
467        )
468        Divider().height(5)
469        // First click event
470        Text('Parent: incr counter[0].counter')
471          .fontSize(20).height(50)
472          .onClick(() => {
473            this.counter[0].incrCounter();
474            // The value increases by 10 each time the event is triggered.
475            this.counter[0].incrSubCounter(10);
476          })
477        // Second click event
478        Text('Parent: set.counter to 10')
479          .fontSize(20).height(50)
480          .onClick(() => {
481            // The value cannot be set to 10, and the UI is not updated.
482            this.counter[0].setSubCounter(10);
483          })
484        Text('Parent: reset entire counter')
485          .fontSize(20).height(50)
486          .onClick(() => {
487            this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
488          })
489      }
490    }
491  }
492}
493```
494
495For the **onClick** event of **Text('Parent: incr counter[0].counter')**, **this.counter[0].incrSubCounter(10)** calls the **incrSubCounter** method to increase the **counter** value of **SubCounter** by 10. The UI is updated to reflect the change.
496
497However, when **this.counter[0].setSubCounter(10)** is called in **onClick** of **Text('Parent: set.counter to 10')**, the **counter** value of **SubCounter** cannot be reset to 10.
498
499**incrSubCounter** and **setSubCounter** are functions of the same **SubCounter**. The UI can be correctly updated when **incrSubCounter** is called for the first click event. However, the UI is not updated when **setSubCounter** is called for the second click event. Actually neither **incrSubCounter** nor **setSubCounter** can trigger an update of **Text('${this.value.subCounter.counter}')**. This is because **\@ObjectLink value: ParentCounter** can only observe the attributes of **ParentCounter**, and **this.value.subCounter.counter** is an attribute of **SubCounter** and therefore cannot be observed.
500
501However, when **this.counter[0].incrCounter()** is called for the first click event, it marks **\@ObjectLink value: ParentCounter** in the **CounterComp** component as changed. In this case, the update of **Text('${this.value.subCounter.counter}')** is triggered. If **this.counter[0].incrCounter()** is deleted from the first click event, the UI cannot be updated.
502
503
504### Recommended
505
506To solve the preceding problem, you can use the following method to directly observe the attributes in **SubCounter** so that the **this.counter[0].setSubCounter(10)** API works:
507
508
509```ts
510@ObjectLink value: ParentCounter = new ParentCounter(0);
511@ObjectLink subValue: SubCounter = new SubCounter(0);
512```
513
514This approach enables \@ObjectLink to serve as a proxy for the attributes of the **ParentCounter** and **SubCounter** classes. In this way, the attribute changes of the two classes can be observed and trigger UI update. Even if **this.counter[0].incrCounter()** is deleted, the UI can be updated correctly.
515
516This method can be used to implement "two-layer" observation, that is, observation of external objects and internal nested objects. However, this method can only be used for the \@ObjectLink decorator and cannot be used for \@Prop (\@Prop passes objects through deep copy). For details, see the differences between @Prop and @ObjectLink.
517
518
519```ts
520let nextId = 1;
521@Observed
522class SubCounter {
523  counter: number;
524  constructor(c: number) {
525    this.counter = c;
526  }
527}
528@Observed
529class ParentCounter {
530  id: number;
531  counter: number;
532  subCounter: SubCounter;
533  incrCounter() {
534    this.counter++;
535  }
536  incrSubCounter(c: number) {
537    this.subCounter.counter += c;
538  }
539  setSubCounter(c: number): void {
540    this.subCounter.counter = c;
541  }
542  constructor(c: number) {
543    this.id = nextId++;
544    this.counter = c;
545    this.subCounter = new SubCounter(c);
546  }
547}
548@Component
549struct CounterComp {
550  @ObjectLink value: ParentCounter;
551  @ObjectLink subValue: SubCounter;
552  build() {
553    Column({ space: 10 }) {
554      Text(`${this.value.counter}`)
555        .fontSize(25)
556        .onClick(() => {
557          this.value.incrCounter();
558        })
559      Text(`${this.subValue.counter}`)
560        .onClick(() => {
561          this.subValue.counter += 1;
562        })
563      Divider().height(2)
564    }
565  }
566}
567@Entry
568@Component
569struct ParentComp {
570  @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
571  build() {
572    Row() {
573      Column() {
574        CounterComp({ value: this.counter[0], subValue: this.counter[0].subCounter })
575        CounterComp({ value: this.counter[1], subValue: this.counter[1].subCounter })
576        CounterComp({ value: this.counter[2], subValue: this.counter[2].subCounter })
577        Divider().height(5)
578        ForEach(this.counter,
579          (item: ParentCounter) => {
580            CounterComp({ value: item, subValue: item.subCounter })
581          },
582          (item: ParentCounter) => item.id.toString()
583        )
584        Divider().height(5)
585        Text('Parent: reset entire counter')
586          .fontSize(20).height(50)
587          .onClick(() => {
588            this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
589          })
590        Text('Parent: incr counter[0].counter')
591          .fontSize(20).height(50)
592          .onClick(() => {
593            this.counter[0].incrCounter();
594            this.counter[0].incrSubCounter(10);
595          })
596        Text('Parent: set.counter to 10')
597          .fontSize(20).height(50)
598          .onClick(() => {
599            this.counter[0].setSubCounter(10);
600          })
601      }
602    }
603  }
604}
605```
606
607
608## Differences Between \@Prop and \@ObjectLink
609
610In the following example, the \@ObjectLink decorated variable is a reference to the data source. That is, **this.value.subValue** and **this.subValue** are different references of the same object. Therefore, when the click handler of **CounterComp** is clicked, both **this.value.subCounter.counter** and **this.subValue.counter** change, and the corresponding component **Text (this.subValue.counter: ${this.subValue.counter})** is re-rendered.
611
612
613```ts
614let nextId = 1;
615
616@Observed
617class SubCounter {
618  counter: number;
619  constructor(c: number) {
620    this.counter = c;
621  }
622}
623
624@Observed
625class ParentCounter {
626  id: number;
627  counter: number;
628  subCounter: SubCounter;
629  incrCounter() {
630    this.counter++;
631  }
632  incrSubCounter(c: number) {
633    this.subCounter.counter += c;
634  }
635  setSubCounter(c: number): void {
636    this.subCounter.counter = c;
637  }
638  constructor(c: number) {
639    this.id = nextId++;
640    this.counter = c;
641    this.subCounter = new SubCounter(c);
642  }
643}
644
645@Component
646struct CounterComp {
647  @ObjectLink value: ParentCounter;
648  @ObjectLink subValue: SubCounter;
649  build() {
650    Column({ space: 10 }) {
651      Text(`this.subValue.counter: ${this.subValue.counter}`)
652        .fontSize(30)
653      Text(`this.value.counter: increase 7 `)
654        .fontSize(30)
655        .onClick(() => {
656          // click handler, Text(`this.subValue.counter: ${this.subValue.counter}`) will update
657          this.value.incrSubCounter(7);
658        })
659      Divider().height(2)
660    }
661  }
662}
663
664@Entry
665@Component
666struct ParentComp {
667  @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
668  build() {
669    Row() {
670      Column() {
671        CounterComp({ value: this.counter[0], subValue: this.counter[0].subCounter })
672        CounterComp({ value: this.counter[1], subValue: this.counter[1].subCounter })
673        CounterComp({ value: this.counter[2], subValue: this.counter[2].subCounter })
674        Divider().height(5)
675        ForEach(this.counter,
676          (item: ParentCounter) => {
677            CounterComp({ value: item, subValue: item.subCounter })
678          },
679          (item: ParentCounter) => item.id.toString()
680        )
681        Divider().height(5)
682        Text('Parent: reset entire counter')
683          .fontSize(20).height(50)
684          .onClick(() => {
685            this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
686          })
687        Text('Parent: incr counter[0].counter')
688          .fontSize(20).height(50)
689          .onClick(() => {
690            this.counter[0].incrCounter();
691            this.counter[0].incrSubCounter(10);
692          })
693        Text('Parent: set.counter to 10')
694          .fontSize(20).height(50)
695          .onClick(() => {
696            this.counter[0].setSubCounter(10);
697          })
698      }
699    }
700  }
701}
702```
703
704Below shows \@ObjectLink working in action.
705
706![en-us_image_0000001651665921](figures/en-us_image_0000001651665921.png)
707
708
709### Not Recommended
710
711If \@Prop is used instead of \@ObjectLink, then: When the first click handler is clicked, the UI is updated properly; However, when the second **onClick** event occurs, the first **Text** component of **CounterComp** is not re-rendered, because \@Prop makes a local copy of the variable.
712
713  **this.value.subCounter** and **this.subValue** are not the same object. Therefore, the change of **this.value.subCounter** does not change the copy object of **this.subValue**, and **Text(this.subValue.counter: ${this.subValue.counter})** is not re-rendered.
714
715```ts
716@Component
717struct CounterComp {
718  @Prop value: ParentCounter = new ParentCounter(0);
719  @Prop subValue: SubCounter = new SubCounter(0);
720  build() {
721    Column({ space: 10 }) {
722      Text(`this.subValue.counter: ${this.subValue.counter}`)
723        .fontSize(20)
724        .onClick(() => {
725          // 1st click handler
726          this.subValue.counter += 7;
727        })
728      Text(`this.value.counter: increase 7 `)
729        .fontSize(20)
730        .onClick(() => {
731          // 2nd click handler
732          this.value.incrSubCounter(7);
733        })
734      Divider().height(2)
735    }
736  }
737}
738```
739
740Below shows \@Prop working in action.
741
742![en-us_image_0000001602146116](figures/en-us_image_0000001602146116.png)
743
744
745### Recommended
746
747Make only one copy of \@Prop value: ParentCounter from **ParentComp** to **CounterComp**. Do not make another copy of **SubCounter**.
748
749- Use only one **\@Prop counter: Counter** in the **CounterComp** component.
750
751- Add another child component **SubCounterComp** that contains **\@ObjectLink subCounter: SubCounter**. This \@ObjectLink ensures that changes to the **SubCounter** object attributes are observed and the UI is updated properly.
752
753- **\@ObjectLink subCounter: SubCounter** shares the same **SubCounter** object with **this.counter.subCounter** of **CounterComp**.
754
755
756```ts
757let nextId = 1;
758
759@Observed
760class SubCounter {
761  counter: number;
762  constructor(c: number) {
763    this.counter = c;
764  }
765}
766
767@Observed
768class ParentCounter {
769  id: number;
770  counter: number;
771  subCounter: SubCounter;
772  incrCounter() {
773    this.counter++;
774  }
775  incrSubCounter(c: number) {
776    this.subCounter.counter += c;
777  }
778  setSubCounter(c: number): void {
779    this.subCounter.counter = c;
780  }
781  constructor(c: number) {
782    this.id = nextId++;
783    this.counter = c;
784    this.subCounter = new SubCounter(c);
785  }
786}
787
788@Component
789struct SubCounterComp {
790  @ObjectLink subValue: SubCounter;
791  build() {
792    Text(`SubCounterComp: this.subValue.counter: ${this.subValue.counter}`)
793      .onClick(() => {
794        // 2nd click handler
795        this.subValue.incrSubCounter(7);
796      })
797  }
798}
799@Component
800struct CounterComp {
801  @ObjectLink value: ParentCounter;
802  build() {
803    Column({ space: 10 }) {
804      Text(`this.value.incrCounter(): this.value.counter: ${this.value.counter}`)
805        .fontSize(20)
806        .onClick(() => {
807          // 1st click handler
808          this.value.incrCounter();
809        })
810      SubCounterComp({ subValue: this.value.subCounter })
811      Text(`this.value.incrSubCounter()`)
812        .onClick(() => {
813          // 3rd click handler
814          this.value.incrSubCounter(77);
815        })
816      Divider().height(2)
817    }
818  }
819}
820@Entry
821@Component
822struct ParentComp {
823  @State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
824  build() {
825    Row() {
826      Column() {
827        CounterComp({ value: this.counter[0] })
828        CounterComp({ value: this.counter[1] })
829        CounterComp({ value: this.counter[2] })
830        Divider().height(5)
831        ForEach(this.counter,
832          (item: ParentCounter) => {
833            CounterComp({ value: item })
834          },
835          (item: ParentCounter) => item.id.toString()
836        )
837        Divider().height(5)
838        Text('Parent: reset entire counter')
839          .fontSize(20).height(50)
840          .onClick(() => {
841            this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
842          })
843        Text('Parent: incr counter[0].counter')
844          .fontSize(20).height(50)
845          .onClick(() => {
846            this.counter[0].incrCounter();
847            this.counter[0].incrSubCounter(10);
848          })
849        Text('Parent: set.counter to 10')
850          .fontSize(20).height(50)
851          .onClick(() => {
852            this.counter[0].setSubCounter(10);
853          })
854      }
855    }
856  }
857}
858```
859
860
861Below shows the copy relationship.
862
863
864![en-us_image_0000001653949465](figures/en-us_image_0000001653949465.png)
865
866
867## Application Not Allowed to Change State Variables During Rendering
868
869Before learning this example, keep in mind that, in ArkUI state management, UI update is driven by state.
870
871![en-us_image_0000001651365257](figures/en-us_image_0000001651365257.png)
872
873Therefore, state variables cannot be directly changed in the **build()** or \@Builder decorated method of a custom component, as this may cause loop rendering risks. The following uses the **build()** method as an example.
874
875
876### Not Recommended
877
878In the following example, **Text('${this.count++}')** directly changes the state variable in the **build()** method.
879
880
881```ts
882@Entry
883@Component
884struct CompA {
885  @State col1: Color = Color.Yellow;
886  @State col2: Color = Color.Green;
887  @State count: number = 1;
888  build() {
889    Column() {
890      // Do not directly change the value of count in the Text component.
891      Text(`${this.count++}`)
892        .width(50)
893        .height(50)
894        .fontColor(this.col1)
895        .onClick(() => {
896          this.col2 = Color.Red;
897        })
898      Button("change col1").onClick(() =>{
899        this.col1 = Color.Pink;
900      })
901    }
902    .backgroundColor(this.col2)
903  }
904}
905```
906
907In ArkUI, the full and minimum updates of **Text('${this.count++}')** impose different effects:
908
909- Full update: ArkUI may fall into an infinite re-rendering loop because each rendering of the **Text** component changes the application state and causes the next rendering to start. When **this.col2** is changed, the entire **build** function is executed. As a result, the text bound to **Text(${this.count++})** is also changed. Each time **Text(${this.count++})** is rendered, the **this.count** state variable is updated, and a new round of **build** execution follows, resulting in an infinite loop.
910
911- Minimum update: When **this.col2** is changed, only the **Column** component is updated, and the **Text** component remains unchanged. When **this.col1** is changed, the entire **Text** component is updated and all of its attribute functions are executed. As a result, the value of **${this.count++}** in the **Text** component is changed. Currently, the UI is updated by component. If an attribute of a component changes, the entire component is updated. Therefore, the overall update link is as follows: **this.col2** = **Color.Red** - > **Text** component update - > **this.count++** - > **Text** component update.
912
913
914### Recommended
915
916When possible, perform the count++ operation in the event handler.
917
918
919```ts
920@Entry
921@Component
922struct CompA {
923  @State col1: Color = Color.Yellow;
924  @State col2: Color = Color.Green;
925  @State count: number = 1;
926  build() {
927    Column() {
928      Text(`${this.count}`)
929        .width(50)
930        .height(50)
931        .backgroundColor(this.col1)
932        .onClick(() => {
933          this.count++;
934        })
935    }
936    .backgroundColor(this.col2)
937  }
938}
939```
940
941The behavior of changing the application state in the **build** function may be more covert than that in the preceding example. The following are some examples:
942
943- Changing the state variable within the \@Builder, \@Extend, or \@Styles decorated method
944
945- Changing the application state variable in the function called during parameter calculation, for example, **Text('${this.calcLabel()}')**
946
947- Modifying the current array: In the following code snippet, **sort()** changes the array **this.arr**, and the subsequent **filter** method returns a new array.
948
949
950```ts
951@State arr : Array<...> = [ ... ];
952ForEach(this.arr.sort().filter(...),
953  item => {
954  ...
955})
956```
957
958In the correct invoking sequence, **filter**, which returns a new array, is called before **sort()**. In this way, the **sort()** method does not change the array **this.arr**.
959
960
961```ts
962ForEach(this.arr.filter(...).sort(),
963  item => {
964  ...
965})
966```
967
968
969## Forcibly Updating Data Through State Variables
970
971
972### Not Recommended
973
974
975```ts
976@Entry
977@Component
978struct CompA {
979  @State needsUpdate: boolean = true;
980  realState1: Array<number> = [4, 1, 3, 2]; // No state variable decorator is used.
981  realState2: Color = Color.Yellow;
982
983  updateUI1(param: Array<number>): Array<number> {
984    const triggerAGet = this.needsUpdate;
985    return param;
986  }
987  updateUI2(param: Color): Color {
988    const triggerAGet = this.needsUpdate;
989    return param;
990  }
991  build() {
992    Column({ space: 20 }) {
993      ForEach(this.updateUI1(this.realState1),
994        (item: Array<number>) => {
995          Text(`${item}`)
996        })
997      Text("add item")
998        .onClick(() => {
999          // Changing realState1 does not trigger UI update.
1000          this.realState1.push(this.realState1[this.realState1.length-1] + 1);
1001
1002          // Trigger the UI update.
1003          this.needsUpdate = !this.needsUpdate;
1004        })
1005      Text("chg color")
1006        .onClick(() => {
1007          // Changing realState2 does not trigger UI update.
1008          this.realState2 = this.realState2 == Color.Yellow ? Color.Red : Color.Yellow;
1009
1010          // Trigger the UI update.
1011          this.needsUpdate = !this.needsUpdate;
1012        })
1013    }.backgroundColor(this.updateUI2(this.realState2))
1014    .width(200).height(500)
1015  }
1016}
1017```
1018
1019The preceding example has the following pitfalls:
1020
1021- The application wants to control the UI update logic, but in ArkUI, the UI update logic should be implemented by the framework detecting changes to the application state variables.
1022
1023- **this.needsUpdate** is a custom state variable that should be applied only to the UI component to which it is bound. Because **this.realState1** and **this.realState2** are regular variables (not decorated), their changes do not trigger UI update.
1024
1025- However, in this application, the user attempts to update the two regular variables through **this.needsUpdate**. This approach is nonviable and may result in poor update performance: The change of **this.needsUpdate** will cause ForEach to update, even if the original intent is to update only the background color.
1026
1027
1028### Recommended
1029
1030To address this issue, decorate the **realState1** and **realState2** variables with \@State. Then, the variable **needsUpdate** is no longer required.
1031
1032
1033```ts
1034@Entry
1035@Component
1036struct CompA {
1037  @State realState1: Array<number> = [4, 1, 3, 2];
1038  @State realState2: Color = Color.Yellow;
1039  build() {
1040    Column({ space: 20 }) {
1041      ForEach(this.realState1,
1042        (item: Array<number>) => {
1043          Text(`${item}`)
1044        })
1045      Text("add item")
1046        .onClick(() => {
1047          // Changing realState1 triggers UI update.
1048          this.realState1.push(this.realState1[this.realState1.length-1] + 1);
1049        })
1050      Text("chg color")
1051        .onClick(() => {
1052          // Changing realState2 triggers UI update.
1053          this.realState2 = this.realState2 == Color.Yellow ? Color.Red : Color.Yellow;
1054        })
1055    }.backgroundColor(this.realState2)
1056    .width(200).height(500)
1057  }
1058}
1059```
1060
1061## Component Reuse
1062
1063If @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. In the following examples, using @Reusable to pass data from the parent component to the child component is recommended, and nesting @Prop with more than five layers of data is not recommended.
1064
1065### Not Recommended
1066
1067```ts
1068// The following is the data structure of a nested class object.
1069@Observed
1070class ClassA {
1071  public title: string;
1072
1073  constructor(title: string) {
1074    this.title = title;
1075  }
1076}
1077
1078@Observed
1079class ClassB {
1080  public name: string;
1081  public a: ClassA;
1082
1083  constructor(name: string, a: ClassA) {
1084    this.name = name;
1085    this.a = a;
1086  }
1087}
1088
1089@Observed
1090class ClassC {
1091  public name: string;
1092  public b: ClassB;
1093
1094  constructor(name: string, b: ClassB) {
1095    this.name = name;
1096    this.b = b;
1097  }
1098}
1099
1100@Observed
1101class ClassD {
1102  public name: string;
1103  public c: ClassC;
1104
1105  constructor(name: string, c: ClassC) {
1106    this.name = name;
1107    this.c = c;
1108  }
1109}
1110
1111@Observed
1112class ClassE {
1113  public name: string;
1114  public d: ClassD;
1115
1116  constructor(name: string, d: ClassD) {
1117    this.name = name;
1118    this.d = d;
1119  }
1120}
1121
1122```
1123
1124The following component hierarchy presents a data structure of nested @Prop.
1125
1126```ts
1127@Entry
1128@Component
1129struct Parent {
1130  @State vote: ClassE = new ClassE('Hi', new ClassD('OpenHarmony', new ClassC('Hello', new ClassB('World', new ClassA('Peace')))))
1131
1132  build() {
1133    Column() {
1134      Button('change')
1135        .onClick(() => {
1136          this.vote.name = "Hello"
1137        })
1138      Child({ voteOne: this.vote })
1139    }
1140  }
1141}
1142
1143@Component
1144struct Child {
1145  @ObjectLink voteOne: ClassE
1146  build() {
1147    Column() {
1148      Text(this.voteOne.name).fontSize(24).fontColor(Color.Red).margin(50)
1149        .onClick(() => {
1150          console.log('this.voteOne.name:' + this.voteOne.name);
1151          this.voteOne.name = 'Bye'
1152        })
1153      ChildOne({voteTwo:this.voteOne.d})
1154    }
1155  }
1156}
1157
1158@Component
1159struct ChildOne {
1160  @ObjectLink voteTwo: ClassD
1161  build() {
1162    Column() {
1163      Text(this.voteTwo.name).fontSize(24).fontColor(Color.Red).margin(50)
1164        .onClick(() => {
1165          console.log('this.voteTwo.name:' + this.voteTwo.name);
1166          this.voteTwo.name = 'Bye Bye'
1167        })
1168      ChildTwo({voteThree:this.voteTwo.c})
1169    }
1170  }
1171}
1172
1173@Component
1174struct ChildTwo {
1175  @ObjectLink voteThree: ClassC
1176  build() {
1177    Column() {
1178      Text(this.voteThree.name).fontSize(24).fontColor(Color.Red).margin(50)
1179        .onClick(() => {
1180          console.log('this.voteThree.name:' + this.voteThree.name);
1181          this.voteThree.name = 'Bye Bye Bye'
1182        })
1183      ChildThree({voteFour:this.voteThree.b})
1184    }
1185  }
1186}
1187
1188@Component
1189struct ChildThree {
1190  @ObjectLink voteFour: ClassB
1191  build() {
1192    Column() {
1193      Text(this.voteFour.name).fontSize(24).fontColor(Color.Red).margin(50)
1194        .onClick(() => {
1195          console.log('this.voteFour.name:' + this.voteFour.name);
1196          this.voteFour.name = 'Bye Bye Bye Bye'
1197        })
1198      ChildFour({voteFive:this.voteFour.a})
1199    }
1200  }
1201}
1202
1203@Component
1204struct ChildFour {
1205  @ObjectLink voteFive: ClassA
1206  build() {
1207    Column() {
1208      Text(this.voteFive.title).fontSize(24).fontColor(Color.Red).margin(50)
1209        .onClick(() => {
1210          console.log('this.voteFive.title:' + this.voteFive.title);
1211          this.voteFive.title = 'Bye Bye Bye Bye Bye'
1212        })
1213    }
1214  }
1215}
1216```
1217
1218### Recommended
1219
1220In component reuse scenarios, 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.
1221
1222```ts
1223// The following is the data structure of a nested class object.
1224@Observed
1225class ClassA {
1226  public title: string;
1227
1228  constructor(title: string) {
1229    this.title = title;
1230  }
1231}
1232
1233@Observed
1234class ClassB {
1235  public name: string;
1236  public a: ClassA;
1237
1238  constructor(name: string, a: ClassA) {
1239    this.name = name;
1240    this.a = a;
1241  }
1242}
1243
1244@Observed
1245class ClassC {
1246  public name: string;
1247  public b: ClassB;
1248
1249  constructor(name: string, b: ClassB) {
1250    this.name = name;
1251    this.b = b;
1252  }
1253}
1254
1255@Observed
1256class ClassD {
1257  public name: string;
1258  public c: ClassC;
1259
1260  constructor(name: string, c: ClassC) {
1261    this.name = name;
1262    this.c = c;
1263  }
1264}
1265
1266@Observed
1267class ClassE {
1268  public name: string;
1269  public d: ClassD;
1270
1271  constructor(name: string, d: ClassD) {
1272    this.name = name;
1273    this.d = d;
1274  }
1275}
1276
1277```
1278
1279The following component hierarchy presents a data structure of @Reusable.
1280
1281```ts
1282// The following is the data structure of a nested class object.
1283@Observed
1284class ClassA {
1285  public title: string;
1286
1287  constructor(title: string) {
1288    this.title = title;
1289  }
1290}
1291
1292@Observed
1293class ClassB {
1294  public name: string;
1295  public a: ClassA;
1296
1297  constructor(name: string, a: ClassA) {
1298    this.name = name;
1299    this.a = a;
1300  }
1301}
1302
1303@Observed
1304class ClassC {
1305  public name: string;
1306  public b: ClassB;
1307
1308  constructor(name: string, b: ClassB) {
1309    this.name = name;
1310    this.b = b;
1311  }
1312}
1313
1314@Observed
1315class ClassD {
1316  public name: string;
1317  public c: ClassC;
1318
1319  constructor(name: string, c: ClassC) {
1320    this.name = name;
1321    this.c = c;
1322  }
1323}
1324
1325@Observed
1326class ClassE {
1327  public name: string;
1328  public d: ClassD;
1329
1330  constructor(name: string, d: ClassD) {
1331    this.name = name;
1332    this.d = d;
1333  }
1334}
1335@Entry
1336@Component
1337struct Parent {
1338  @State vote: ClassE = new ClassE('Hi', new ClassD('OpenHarmony', new ClassC('Hello', new ClassB('World', new ClassA('Peace')))))
1339
1340  build() {
1341    Column() {
1342      Button('change')
1343        .onClick(() => {
1344          this.vote.name = "Hello"
1345        })
1346        .reuseId(Child.name)
1347      Child({voteOne: this.vote})
1348    }
1349  }
1350}
1351
1352@Reusable
1353@Component
1354struct Child {
1355  @State voteOne: ClassE = new ClassE('voteOne', new ClassD('OpenHarmony', new ClassC('Hello', new ClassB('World', new ClassA('Peace')))))
1356
1357  aboutToReuse(params: ClassE) {
1358    this.voteOne = params
1359  }
1360  build() {
1361    Column() {
1362      Text(this.voteOne.name).fontSize(24).fontColor(Color.Red).margin(50)
1363        .onClick(() => {
1364          console.error('this.voteOne.name:' + this.voteOne.name);
1365          this.voteOne.name = 'Bye'
1366        })
1367        .reuseId(ChildOne.name)
1368      ChildOne({voteTwo: this.voteOne.d})
1369    }
1370  }
1371}
1372
1373@Reusable
1374@Component
1375struct ChildOne {
1376  @State voteTwo: ClassD = new ClassD('voteTwo', new ClassC('Hello', new ClassB('World', new ClassA('Peace'))))
1377  aboutToReuse(params: ClassD){
1378    this.voteTwo = params
1379  }
1380  build() {
1381    Column() {
1382      Text(this.voteTwo.name).fontSize(24).fontColor(Color.Red).margin(50)
1383        .onClick(() => {
1384          console.error('this.voteTwo.name:' + this.voteTwo.name);
1385          this.voteTwo.name = 'Bye Bye'
1386        })
1387        .reuseId(ChildTwo.name)
1388      ChildTwo({voteThree: this.voteTwo.c})
1389    }
1390  }
1391}
1392
1393@Reusable
1394@Component
1395struct ChildTwo {
1396  @State voteThree: ClassC = new ClassC('voteThree', new ClassB('World', new ClassA('Peace')))
1397  aboutToReuse(params: ClassC){
1398    this.voteThree = params
1399
1400  }
1401  build() {
1402    Column() {
1403      Text(this.voteThree.name).fontSize(24).fontColor(Color.Red).margin(50)
1404        .onClick(() => {
1405          console.log('this.voteThree.name:' + this.voteThree.name);
1406          this.voteThree.name = 'Bye Bye Bye'
1407        })
1408        .reuseId(ChildThree.name)
1409      ChildThree({voteFour: this.voteThree.b})
1410    }
1411  }
1412}
1413
1414@Reusable
1415@Component
1416struct ChildThree {
1417  @State voteFour: ClassB = new ClassB('voteFour', new ClassA('Peace'))
1418  aboutToReuse(params: ClassB){
1419    this.voteFour = params
1420
1421  }
1422  build() {
1423    Column() {
1424      Text(this.voteFour.name).fontSize(24).fontColor(Color.Red).margin(50)
1425        .onClick(() => {
1426          console.log('this.voteFour.name:' + this.voteFour.name);
1427          this.voteFour.name = 'Bye Bye Bye Bye'
1428        })
1429        .reuseId(ChildFour.name)
1430      ChildFour({voteFive: this.voteFour.a})
1431    }
1432  }
1433}
1434
1435@Reusable
1436@Component
1437struct ChildFour {
1438  @State voteFive: ClassA = new ClassA('voteFive')
1439  aboutToReuse(params: ClassA){
1440    this.voteFive = params
1441
1442  }
1443  build() {
1444    Column() {
1445      Text(this.voteFive.title).fontSize(24).fontColor(Color.Red).margin(50)
1446        .onClick(() => {
1447          console.log('this.voteFive.title:' + this.voteFive.title);
1448          this.voteFive.title = 'Bye Bye Bye Bye Bye'
1449        })
1450    }
1451  }
1452}
1453```
1454