1# Proper Use of State Management
2
3Managing state in applications can be a tricky task. You may find the UI not refreshed as expected, or the re-renders slowing down your application. This topic explores some best practices for managing state, through typical correct and incorrect usage examples.
4
5## Properly Using Attributes
6
7### Combining Simple Attributes into Object Arrays
8
9It is commonplace in development to set the same attribute for multiple components, for example, the text content, width, or height attributes. To make these attributes easier to manage, you can store them in an array and use them with **ForEach**.
10
11```typescript
12@Entry
13@Component
14struct Index {
15  @State items: string[] = [];
16  @State ids: string[] = [];
17  @State age: number[] = [];
18  @State gender: string[] = [];
19
20  aboutToAppear() {
21    this.items.push("Head");
22    this.items.push("List");
23    for (let i = 0; i < 20; i++) {
24      this.ids.push("id: " + Math.floor(Math.random() * 1000));
25      this.age.push(Math.floor(Math.random() * 100 % 40));
26      this.gender.push(Math.floor(Math.random() * 100) % 2 == 0 ? "Male" : "Female");
27    }
28  }
29
30  isRenderText(index: number) : number {
31    console.log(`index ${index} is rendered`);
32    return 1;
33  }
34
35  build() {
36    Row() {
37      Column() {
38        ForEach(this.items, (item: string) => {
39          if (item == "Head") {
40            Text("Personal Info")
41              .fontSize(40)
42          } else if (item == "List") {
43            List() {
44              ForEach(this.ids, (id: string, index) => {
45                ListItem() {
46                  Row() {
47                    Text(id)
48                      .fontSize(20)
49                      .margin({
50                        left: 30,
51                        right: 5
52                      })
53                    Text("age: " + this.age[index as number])
54                      .fontSize(20)
55                      .margin({
56                        left: 5,
57                        right: 5
58                      })
59                      .position({x: 100})
60                      .opacity(this.isRenderText(index))
61                      .onClick(() => {
62                        this.age[index]++;
63                      })
64                    Text("gender: " + this.gender[index as number])
65                      .margin({
66                        left: 5,
67                        right: 5
68                      })
69                      .position({x: 180})
70                      .fontSize(20)
71                  }
72                }
73                .margin({
74                  top: 5,
75                  bottom: 5
76                })
77              })
78            }
79          }
80        })
81      }
82    }
83  }
84}
85```
86
87Below you can see how the preceding code snippet works.
88
89![properly-use-state-management-to-develope-1](figures/properly-use-state-management-to-develope-1.gif)
90
91In this example, a total of 20 records are displayed on the page through **ForEach**. When you click the **Text** component of **age** in one of the records, the **Text** components of **age** in other 19 records are also re-rendered - reflected by the logs generated for the components of **age**. However, because the **age** values of the other 19 records do not change, the re-rendering of these records is actually redundant.
92
93This redundant re-rendering is due to a characteristic of state management. Assume that there is an @State decorated number array **Num[]**. This array contains 20 elements whose values are 0 to 19, respectively. Each of the 20 elements is bound to a **Text** component. When one of the elements is changed, all components bound to the elements are re-rendered, regardless of whether the other elements are changed or not.
94
95This seemly bug, commonly known as "redundant re-render", is widely observed in simple array, and can adversely affect the UI re-rendering performance when the arrays are large. To make your rendering process run smoothly, it is crucial to reduce redundant re-renders and update components only when necessary.
96
97In the case of an array of simple attributes, you can avoid redundant re-rendering by converting the array into an object array. The code snippet after optimization is as follows:
98
99```typescript
100@Observed
101class InfoList extends Array<Info> {
102};
103@Observed
104class Info {
105  ids: number;
106  age: number;
107  gender: string;
108
109  constructor() {
110    this.ids = Math.floor(Math.random() * 1000);
111    this.age = Math.floor(Math.random() * 100 % 40);
112    this.gender = Math.floor(Math.random() * 100) % 2 == 0 ? "Male" : "Female";
113  }
114}
115@Component
116struct Information {
117  @ObjectLink info: Info;
118  @State index: number = 0;
119  isRenderText(index: number) : number {
120    console.log(`index ${index} is rendered`);
121    return 1;
122  }
123
124  build() {
125    Row() {
126      Text("id: " + this.info.ids)
127        .fontSize(20)
128        .margin({
129          left: 30,
130          right: 5
131        })
132      Text("age: " + this.info.age)
133        .fontSize(20)
134        .margin({
135          left: 5,
136          right: 5
137        })
138        .position({x: 100})
139        .opacity(this.isRenderText(this.index))
140        .onClick(() => {
141          this.info.age++;
142        })
143      Text("gender: " + this.info.gender)
144        .margin({
145          left: 5,
146          right: 5
147        })
148        .position({x: 180})
149        .fontSize(20)
150    }
151  }
152}
153@Entry
154@Component
155struct Page {
156  @State infoList: InfoList = new InfoList();
157  @State items: string[] = [];
158  aboutToAppear() {
159    this.items.push("Head");
160    this.items.push("List");
161    for (let i = 0; i < 20; i++) {
162      this.infoList.push(new Info());
163    }
164  }
165
166  build() {
167    Row() {
168      Column() {
169        ForEach(this.items, (item: string) => {
170          if (item == "Head") {
171            Text("Personal Info")
172              .fontSize(40)
173          } else if (item == "List") {
174            List() {
175              ForEach(this.infoList, (info: Info, index) => {
176                ListItem() {
177                  Information({
178                    info: info,
179                    index: index
180                  })
181                }
182                .margin({
183                  top: 5,
184                  bottom: 5
185                })
186              })
187            }
188          }
189        })
190      }
191    }
192  }
193}
194```
195
196Below you can see how the preceding code snippet works.
197
198![properly-use-state-management-to-develope-2](figures/properly-use-state-management-to-develope-2.gif)
199
200After optimization, an object array is used in place of the original attribute arrays. For an array, changes in an object cannot be observed and therefore do not cause re-renders. Specifically, only changes at the top level of array items can be observed, for example, adding, modifying, or deleting an item. For a common array, modifying a data item means to change the item's value. For an object array, it means to assign a new value to the entire object, which means that changes to a property in an object are not observable to the array and consequently do not cause a re-render. In addition to property changes in object arrays, changes in nested objects cannot be observed either, which is further detailed in [Splitting a Complex Large Object into Multiple Small Objects](#splitting-a-complex-large-object-into-multiple-small-objects). In the code after optimization, you may notice a combination of custom components and **ForEach**. For details, see [Using Custom Components to Match Object Arrays in ForEach](#using-custom-components-to-match-object-arrays-in-foreach).
201
202### Splitting a Complex Large Object into Multiple Small Objects
203
204> **NOTE**
205>
206> You are advised to use the [@Track](arkts-track.md) decorator in this scenario since API version 11.
207
208During development, we sometimes define a large object that contains many style-related properties, and pass the object between parent and child components to bind the properties to the components.
209
210```typescript
211@Observed
212class UIStyle {
213  translateX: number = 0;
214  translateY: number = 0;
215  scaleX: number = 0.3;
216  scaleY: number = 0.3;
217  width: number = 336;
218  height: number = 178;
219  posX: number = 10;
220  posY: number = 50;
221  alpha: number = 0.5;
222  borderRadius: number = 24;
223  imageWidth: number = 78;
224  imageHeight: number = 78;
225  translateImageX: number = 0;
226  translateImageY: number = 0;
227  fontSize: number = 20;
228}
229@Component
230struct SpecialImage {
231  @ObjectLink uiStyle: UIStyle;
232  private isRenderSpecialImage() : number { // A function indicating whether the component is rendered.
233    console.log("SpecialImage is rendered");
234    return 1;
235  }
236  build() {
237    Image($r('app.media.icon')) // Use app.media.app_icon since API version 12.
238      .width(this.uiStyle.imageWidth)
239      .height(this.uiStyle.imageHeight)
240      .margin({ top: 20 })
241      .translate({
242        x: this.uiStyle.translateImageX,
243        y: this.uiStyle.translateImageY
244      })
245      .opacity(this.isRenderSpecialImage()) // If the image is re-rendered, this function will be called.
246  }
247}
248@Component
249struct CompA {
250  @ObjectLink uiStyle: UIStyle
251  // The following function is used to display whether the component is rendered.
252  private isRenderColumn() : number {
253    console.log("Column is rendered");
254    return 1;
255  }
256  private isRenderStack() : number {
257    console.log("Stack is rendered");
258    return 1;
259  }
260  private isRenderImage() : number {
261    console.log("Image is rendered");
262    return 1;
263  }
264  private isRenderText() : number {
265    console.log("Text is rendered");
266    return 1;
267  }
268  build() {
269    Column() {
270      SpecialImage({
271        uiStyle: this.uiStyle
272      })
273      Stack() {
274        Column() {
275            Image($r('app.media.icon')) // Use app.media.app_icon since API version 12.
276              .opacity(this.uiStyle.alpha)
277              .scale({
278                x: this.uiStyle.scaleX,
279                y: this.uiStyle.scaleY
280              })
281              .padding(this.isRenderImage())
282              .width(300)
283              .height(300)
284        }
285        .width('100%')
286        .position({ y: -80 })
287        Stack() {
288          Text("Hello World")
289            .fontColor("#182431")
290            .fontWeight(FontWeight.Medium)
291            .fontSize(this.uiStyle.fontSize)
292            .opacity(this.isRenderText())
293            .margin({ top: 12 })
294        }
295        .opacity(this.isRenderStack())
296        .position({
297          x: this.uiStyle.posX,
298          y: this.uiStyle.posY
299        })
300        .width('100%')
301        .height('100%')
302      }
303      .margin({ top: 50 })
304      .borderRadius(this.uiStyle.borderRadius)
305      .opacity(this.isRenderStack())
306      .backgroundColor("#FFFFFF")
307      .width(this.uiStyle.width)
308      .height(this.uiStyle.height)
309      .translate({
310        x: this.uiStyle.translateX,
311        y: this.uiStyle.translateY
312      })
313      Column() {
314        Button("Move")
315          .width(312)
316          .fontSize(20)
317          .backgroundColor("#FF007DFF")
318          .margin({ bottom: 10 })
319          .onClick(() => {
320            animateTo({
321              duration: 500
322            },() => {
323              this.uiStyle.translateY = (this.uiStyle.translateY + 180) % 250;
324            })
325          })
326        Button("Scale")
327          .borderRadius(20)
328          .backgroundColor("#FF007DFF")
329          .fontSize(20)
330          .width(312)
331          .onClick(() => {
332            this.uiStyle.scaleX = (this.uiStyle.scaleX + 0.6) % 0.8;
333          })
334      }
335      .position({
336        y:666
337      })
338      .height('100%')
339      .width('100%')
340
341    }
342    .opacity(this.isRenderColumn())
343    .width('100%')
344    .height('100%')
345
346  }
347}
348@Entry
349@Component
350struct Page {
351  @State uiStyle: UIStyle = new UIStyle();
352  build() {
353    Stack() {
354      CompA({
355        uiStyle: this.uiStyle
356      })
357    }
358    .backgroundColor("#F1F3F5")
359  }
360}
361```
362
363Below you can see how the preceding code snippet works.
364
365![properly-use-state-management-to-develope-3](figures/properly-use-state-management-to-develope-3.gif)
366
367Click the **Move** button before optimization. The duration for updating dirty nodes is as follows.
368
369![img](figures/properly-use-state-management-to-develope-11.PNG)
370
371In the above example, **uiStyle** defines multiple properties, which are each associated with multiple components. When some of these properties are changed at the click of a button, all the components associated with **uiStyle** are re-rendered, even though they do not need to (because the properties of these components are not changed). The re-renders of these components can be observed through a series of defined **isRender** functions. When **Move** is clicked to perform the translation animation, the value of **translateY** changes multiple times. As a result, redundant re-renders occur at each frame, which greatly worsen the application performance.
372
373Such redundant re-renders result from an update mechanism of the state management: If multiple properties of a class are bound to different components through an object of the class, then, if any of the properties is changed, the component associated with the property is re-rendered, together with components associated with the other properties, even though the other properties do not change.
374
375Naturally, this update mechanism brings down the re-rendering performance, especially in the case of a large, complex object associated with a considerable number of components. To fix this issue, split a large, complex object into a set of multiple small objects. In this way, redundant re-renders are reduced and the render scope precisely controlled, while the original code structure is retained.
376
377```typescript
378@Observed
379class NeedRenderImage { // Properties used in the same component can be classified into the same class.
380  public translateImageX: number = 0;
381  public translateImageY: number = 0;
382  public imageWidth:number = 78;
383  public imageHeight:number = 78;
384}
385@Observed
386class NeedRenderScale { // Properties used together can be classified into the same class.
387  public scaleX: number = 0.3;
388  public scaleY: number = 0.3;
389}
390@Observed
391class NeedRenderAlpha { // Properties used separately can be classified into the same class.
392  public alpha: number = 0.5;
393}
394@Observed
395class NeedRenderSize { // Properties used together can be classified into the same class.
396  public width: number = 336;
397  public height: number = 178;
398}
399@Observed
400class NeedRenderPos { // Properties used together can be classified into the same class.
401  public posX: number = 10;
402  public posY: number = 50;
403}
404@Observed
405class NeedRenderBorderRadius { // Properties used separately can be classified into the same class.
406  public borderRadius: number = 24;
407}
408@Observed
409class NeedRenderFontSize { // Properties used separately can be classified into the same class.
410  public fontSize: number = 20;
411}
412@Observed
413class NeedRenderTranslate { // Properties used together can be classified into the same class.
414  public translateX: number = 0;
415  public translateY: number = 0;
416}
417@Observed
418class UIStyle {
419  // Use the NeedRenderxxx class.
420  needRenderTranslate: NeedRenderTranslate = new NeedRenderTranslate();
421  needRenderFontSize: NeedRenderFontSize = new NeedRenderFontSize();
422  needRenderBorderRadius: NeedRenderBorderRadius = new NeedRenderBorderRadius();
423  needRenderPos: NeedRenderPos = new NeedRenderPos();
424  needRenderSize: NeedRenderSize = new NeedRenderSize();
425  needRenderAlpha: NeedRenderAlpha = new NeedRenderAlpha();
426  needRenderScale: NeedRenderScale = new NeedRenderScale();
427  needRenderImage: NeedRenderImage = new NeedRenderImage();
428}
429@Component
430struct SpecialImage {
431  @ObjectLink uiStyle : UIStyle;
432  @ObjectLink needRenderImage: NeedRenderImage // Receive a new class from its parent component.
433  private isRenderSpecialImage() : number { // A function indicating whether the component is rendered.
434    console.log("SpecialImage is rendered");
435    return 1;
436  }
437  build() {
438    Image($r('app.media.icon')) // Use app.media.app_icon since API version 12.
439      .width(this.needRenderImage.imageWidth) // Use this.needRenderImage.xxx.
440      .height(this.needRenderImage.imageHeight)
441      .margin({top:20})
442      .translate({
443        x: this.needRenderImage.translateImageX,
444        y: this.needRenderImage.translateImageY
445      })
446      .opacity(this.isRenderSpecialImage()) // If the image is re-rendered, this function will be called.
447  }
448}
449@Component
450struct CompA {
451  @ObjectLink uiStyle: UIStyle;
452  @ObjectLink needRenderTranslate: NeedRenderTranslate; // Receive the newly defined instance of the NeedRenderxxx class from its parent component.
453  @ObjectLink needRenderFontSize: NeedRenderFontSize;
454  @ObjectLink needRenderBorderRadius: NeedRenderBorderRadius;
455  @ObjectLink needRenderPos: NeedRenderPos;
456  @ObjectLink needRenderSize: NeedRenderSize;
457  @ObjectLink needRenderAlpha: NeedRenderAlpha;
458  @ObjectLink needRenderScale: NeedRenderScale;
459  // The following function is used to display whether the component is rendered.
460  private isRenderColumn() : number {
461    console.log("Column is rendered");
462    return 1;
463  }
464  private isRenderStack() : number {
465    console.log("Stack is rendered");
466    return 1;
467  }
468  private isRenderImage() : number {
469    console.log("Image is rendered");
470    return 1;
471  }
472  private isRenderText() : number {
473    console.log("Text is rendered");
474    return 1;
475  }
476  build() {
477    Column() {
478      SpecialImage({
479        uiStyle: this.uiStyle,
480        needRenderImage: this.uiStyle.needRenderImage // Pass the needRenderxxx class to the child component.
481      })
482      Stack() {
483        Column() {
484          Image($r('app.media.icon')) // Use app.media.app_icon since API version 12.
485            .opacity(this.needRenderAlpha.alpha)
486            .scale({
487              x: this.needRenderScale.scaleX, // Use this.needRenderXxx.xxx.
488              y: this.needRenderScale.scaleY
489            })
490            .padding(this.isRenderImage())
491            .width(300)
492            .height(300)
493        }
494        .width('100%')
495        .position({ y: -80 })
496
497        Stack() {
498          Text("Hello World")
499            .fontColor("#182431")
500            .fontWeight(FontWeight.Medium)
501            .fontSize(this.needRenderFontSize.fontSize)
502            .opacity(this.isRenderText())
503            .margin({ top: 12 })
504        }
505        .opacity(this.isRenderStack())
506        .position({
507          x: this.needRenderPos.posX,
508          y: this.needRenderPos.posY
509        })
510        .width('100%')
511        .height('100%')
512      }
513      .margin({ top: 50 })
514      .borderRadius(this.needRenderBorderRadius.borderRadius)
515      .opacity(this.isRenderStack())
516      .backgroundColor("#FFFFFF")
517      .width(this.needRenderSize.width)
518      .height(this.needRenderSize.height)
519      .translate({
520        x: this.needRenderTranslate.translateX,
521        y: this.needRenderTranslate.translateY
522      })
523
524      Column() {
525        Button("Move")
526          .width(312)
527          .fontSize(20)
528          .backgroundColor("#FF007DFF")
529          .margin({ bottom: 10 })
530          .onClick(() => {
531            animateTo({
532              duration: 500
533            }, () => {
534              this.needRenderTranslate.translateY = (this.needRenderTranslate.translateY + 180) % 250;
535            })
536          })
537        Button("Scale")
538          .borderRadius(20)
539          .backgroundColor("#FF007DFF")
540          .fontSize(20)
541          .width(312)
542          .margin({ bottom: 10 })
543          .onClick(() => {
544            this.needRenderScale.scaleX = (this.needRenderScale.scaleX + 0.6) % 0.8;
545          })
546        Button("Change Image")
547          .borderRadius(20)
548          .backgroundColor("#FF007DFF")
549          .fontSize(20)
550          .width(312)
551          .onClick(() => { // Use this.uiStyle.endRenderXxx.xxx to change the property in the parent component.
552            this.uiStyle.needRenderImage.imageWidth = (this.uiStyle.needRenderImage.imageWidth + 30) % 160;
553            this.uiStyle.needRenderImage.imageHeight = (this.uiStyle.needRenderImage.imageHeight + 30) % 160;
554          })
555      }
556      .position({
557        y: 616
558      })
559      .height('100%')
560      .width('100%')
561    }
562    .opacity(this.isRenderColumn())
563    .width('100%')
564    .height('100%')
565  }
566}
567@Entry
568@Component
569struct Page {
570  @State uiStyle: UIStyle = new UIStyle();
571  build() {
572    Stack() {
573      CompA({
574        uiStyle: this.uiStyle,
575        needRenderTranslate: this.uiStyle.needRenderTranslate, // Pass the needRenderxxx class to the child component.
576        needRenderFontSize: this.uiStyle.needRenderFontSize,
577        needRenderBorderRadius: this.uiStyle.needRenderBorderRadius,
578        needRenderPos: this.uiStyle.needRenderPos,
579        needRenderSize: this.uiStyle.needRenderSize,
580        needRenderAlpha: this.uiStyle.needRenderAlpha,
581        needRenderScale: this.uiStyle.needRenderScale
582      })
583    }
584    .backgroundColor("#F1F3F5")
585  }
586}
587```
588
589Below you can see how the preceding code snippet works.![properly-use-state-management-to-develope-4](figures/properly-use-state-management-to-develope-4.gif)
590
591Click the **Move** button after optimization. The duration for updating dirty nodes is as follows.
592
593![img](figures/properly-use-state-management-to-develope-12.PNG)
594
595After the optimization, the 15 attributes previously in one class are divided into eight classes, and the bound components are adapted accordingly. The division of properties complies with the following principles:
596
597- Properties that are only used in the same component can be divided into the same new child class, that is, **NeedRenderImage** in the example. This mode of division is applicable to the scenario where components are frequently re-rendered due to changes of unassociated properties.
598- Properties that are frequently used together can be divided into the same new child class, that is, **NeedRenderScale**, **NeedRenderTranslate**, **NeedRenderPos**, and **NeedRenderSize** in the example. This mode of division is applicable to the scenario where properties often appear in pairs or are applied to the same style, for example, **.translate**, **.position**, and **.scale** (which usually receive an object as a parameter).
599- Properties that may be used in different places should be divided into a new child class, that is, **NeedRenderAlpha**, **NeedRenderBorderRadius**, and **NeedRenderFontSize** in the example. This mode of division is applicable to the scenario where a property works on multiple components or works on their own, for example, **.opacity** and **.borderRadius** (which usually work on their own).
600
601As in combination of properties, the principle behind division of properties is that changes to properties of objects nested more than two levels deep cannot be observed. Yet, you can use @Observed and @ObjectLink to transfer level-2 objects between parent and child nodes to observe property changes at level 2 and precisely control the render scope. <!--Del-->For details about the division of properties, see [Precisely Controlling Render Scope](https://gitee.com/openharmony/docs/blob/master/en/application-dev/performance/precisely-control-render-scope.md).<!--DelEnd-->
602
603@Track decorator can also precisely control the render scope, which does not involve division of properties.
604
605```ts
606@Observed
607class UIStyle {
608  @Track translateX: number = 0;
609  @Track translateY: number = 0;
610  @Track scaleX: number = 0.3;
611  @Track scaleY: number = 0.3;
612  @Track width: number = 336;
613  @Track height: number = 178;
614  @Track posX: number = 10;
615  @Track posY: number = 50;
616  @Track alpha: number = 0.5;
617  @Track borderRadius: number = 24;
618  @Track imageWidth: number = 78;
619  @Track imageHeight: number = 78;
620  @Track translateImageX: number = 0;
621  @Track translateImageY: number = 0;
622  @Track fontSize: number = 20;
623}
624@Component
625struct SpecialImage {
626  @ObjectLink uiStyle: UIStyle;
627  private isRenderSpecialImage() : number { // A function indicating whether the component is rendered.
628    console.log("SpecialImage is rendered");
629    return 1;
630  }
631  build() {
632    Image($r('app.media.icon')) // Use app.media.app_icon since API version 12.
633      .width(this.uiStyle.imageWidth)
634      .height(this.uiStyle.imageHeight)
635      .margin({ top: 20 })
636      .translate({
637        x: this.uiStyle.translateImageX,
638        y: this.uiStyle.translateImageY
639      })
640      .opacity(this.isRenderSpecialImage()) // If the image is re-rendered, this function will be called.
641  }
642}
643@Component
644struct CompA {
645  @ObjectLink uiStyle: UIStyle
646  // The following function is used to display whether the component is rendered.
647  private isRenderColumn() : number {
648    console.log("Column is rendered");
649    return 1;
650  }
651  private isRenderStack() : number {
652    console.log("Stack is rendered");
653    return 1;
654  }
655  private isRenderImage() : number {
656    console.log("Image is rendered");
657    return 1;
658  }
659  private isRenderText() : number {
660    console.log("Text is rendered");
661    return 1;
662  }
663  build() {
664    Column() {
665      SpecialImage({
666        uiStyle: this.uiStyle
667      })
668      Stack() {
669        Column() {
670            Image($r('app.media.icon')) // Use app.media.app_icon since API version 12.
671              .opacity(this.uiStyle.alpha)
672              .scale({
673                x: this.uiStyle.scaleX,
674                y: this.uiStyle.scaleY
675              })
676              .padding(this.isRenderImage())
677              .width(300)
678              .height(300)
679        }
680        .width('100%')
681        .position({ y: -80 })
682        Stack() {
683          Text("Hello World")
684            .fontColor("#182431")
685            .fontWeight(FontWeight.Medium)
686            .fontSize(this.uiStyle.fontSize)
687            .opacity(this.isRenderText())
688            .margin({ top: 12 })
689        }
690        .opacity(this.isRenderStack())
691        .position({
692          x: this.uiStyle.posX,
693          y: this.uiStyle.posY
694        })
695        .width('100%')
696        .height('100%')
697      }
698      .margin({ top: 50 })
699      .borderRadius(this.uiStyle.borderRadius)
700      .opacity(this.isRenderStack())
701      .backgroundColor("#FFFFFF")
702      .width(this.uiStyle.width)
703      .height(this.uiStyle.height)
704      .translate({
705        x: this.uiStyle.translateX,
706        y: this.uiStyle.translateY
707      })
708      Column() {
709        Button("Move")
710          .width(312)
711          .fontSize(20)
712          .backgroundColor("#FF007DFF")
713          .margin({ bottom: 10 })
714          .onClick(() => {
715            animateTo({
716              duration: 500
717            },() => {
718              this.uiStyle.translateY = (this.uiStyle.translateY + 180) % 250;
719            })
720          })
721        Button("Scale")
722          .borderRadius(20)
723          .backgroundColor("#FF007DFF")
724          .fontSize(20)
725          .width(312)
726          .onClick(() => {
727            this.uiStyle.scaleX = (this.uiStyle.scaleX + 0.6) % 0.8;
728          })
729      }
730      .position({
731        y:666
732      })
733      .height('100%')
734      .width('100%')
735
736    }
737    .opacity(this.isRenderColumn())
738    .width('100%')
739    .height('100%')
740
741  }
742}
743@Entry
744@Component
745struct Page {
746  @State uiStyle: UIStyle = new UIStyle();
747  build() {
748    Stack() {
749      CompA({
750        uiStyle: this.uiStyle
751      })
752    }
753    .backgroundColor("#F1F3F5")
754  }
755}
756```
757
758
759
760### Binding Components to Class Objects Decorated with @Observed or Declared as State Variables
761
762Your application may sometimes allow users to reset data - by assigning a new object to the target state variable. The type of the new object is the trick here: If not handled carefully, it may result in the UI not being re-rendered as expected.
763
764```typescript
765@Observed
766class Child {
767  count: number;
768  constructor(count: number) {
769    this.count = count
770  }
771}
772@Observed
773class ChildList extends Array<Child> {
774};
775@Observed
776class Ancestor {
777  childList: ChildList;
778  constructor(childList: ChildList) {
779    this.childList = childList;
780  }
781  public loadData() {
782    let tempList = [new Child(1), new Child(2), new Child(3), new Child(4), new Child(5)];
783    this.childList = tempList;
784  }
785
786  public clearData() {
787    this.childList = []
788  }
789}
790@Component
791struct CompChild {
792  @Link childList: ChildList;
793  @ObjectLink child: Child;
794
795  build() {
796    Row() {
797      Text(this.child.count+'')
798        .height(70)
799        .fontSize(20)
800        .borderRadius({
801          topLeft: 6,
802          topRight: 6
803        })
804        .margin({left: 50})
805      Button('X')
806        .backgroundColor(Color.Red)
807        .onClick(()=>{
808          let index = this.childList.findIndex((item) => {
809            return item.count === this.child.count
810          })
811          if (index !== -1) {
812            this.childList.splice(index, 1);
813          }
814        })
815        .margin({
816          left: 200,
817          right:30
818        })
819    }
820    .margin({
821      top:15,
822      left: 15,
823      right:10,
824      bottom:15
825    })
826    .borderRadius(6)
827    .backgroundColor(Color.Grey)
828  }
829}
830@Component
831struct CompList {
832  @ObjectLink@Watch('changeChildList') childList: ChildList;
833
834  changeChildList() {
835    console.log('CompList ChildList change');
836  }
837
838  isRenderCompChild(index: number) : number {
839    console.log("Comp Child is render" + index);
840    return 1;
841  }
842
843  build() {
844    Column() {
845      List() {
846        ForEach(this.childList, (item: Child, index) => {
847          ListItem() {
848            CompChild({
849              childList: this.childList,
850              child: item
851            })
852              .opacity(this.isRenderCompChild(index))
853          }
854
855        })
856      }
857      .height('70%')
858    }
859  }
860}
861@Component
862struct CompAncestor {
863  @ObjectLink ancestor: Ancestor;
864
865  build() {
866    Column() {
867      CompList({ childList: this.ancestor.childList })
868      Row() {
869        Button("Clear")
870          .onClick(() => {
871            this.ancestor.clearData()
872          })
873          .width(100)
874          .margin({right: 50})
875        Button("Recover")
876          .onClick(() => {
877            this.ancestor.loadData()
878          })
879          .width(100)
880      }
881    }
882  }
883}
884@Entry
885@Component
886struct Page {
887  @State childList: ChildList = [new Child(1), new Child(2), new Child(3), new Child(4),new Child(5)];
888  @State ancestor: Ancestor = new Ancestor(this.childList)
889
890  build() {
891    Column() {
892      CompAncestor({ ancestor: this.ancestor})
893    }
894  }
895}
896```
897
898Below you can see how the preceding code snippet works.
899
900![properly-use-state-management-to-develope-5](figures/properly-use-state-management-to-develope-5.gif)
901
902In the code there is a data source of the ChildList type. If you click **X** to delete some data and then click **Recover** to restore **ChildList**, the UI is not re-rendered after you click **X** again, and no "CompList ChildList change" log is printed.
903
904An examination of the code finds out that when a value is re-assigned to the data source **ChildList** through the **loadData** method of the **Ancestor** object.
905
906```typescript
907  public loadData() {
908    let tempList = [new Child(1), new Child(2), new Child(3), new Child(4), new Child(5)];
909    this.childList = tempList;
910  }
911```
912
913In the **loadData** method, **tempList**, a temporary array of the Child type, is created, to which the member variable **ChildList** of the **Ancestor** object is pointed. However, value changes of the **tempList** array cannot be observed. In other words, its value changes do not cause UI re-renders. After the array is assigned to **childList**, the **ForEach** view is updated and the UI is re-rendered. When you click **X** again, however, the UI is not re-rendered to reflect the decrease in **childList**, because **childList** points to a new, unobservable **tempList**.
914
915You may notice that **childList** is initialized in the same way when it is defined in **Page**.
916
917```typescript
918@State childList: ChildList = [new Child(1), new Child(2), new Child(3), new Child(4),new Child(5)];
919@State ancestor: Ancestor = new Ancestor(this.childList)
920```
921
922Yet, **childList** there is observable, being decorated by @State. As such, while it is assigned an array of the Child[] type not decorated by @Observed, its value changes can cause UI re-renders. If the @State decorator is removed from **childList**, the data source is not reset and UI re-renders cannot be triggered by clicking the **X** button.
923
924In summary, for the UI to be re-rendered properly upon value changes of class objects, these class objects must be observable.
925
926```typescript
927@Observed
928class Child {
929  count: number;
930  constructor(count: number) {
931    this.count = count
932  }
933}
934@Observed
935class ChildList extends Array<Child> {
936};
937@Observed
938class Ancestor {
939  childList: ChildList;
940  constructor(childList: ChildList) {
941    this.childList = childList;
942  }
943  public loadData() {
944    let tempList = new ChildList();
945    for (let i = 1; i < 6; i ++) {
946      tempList.push(new Child(i));
947    }
948    this.childList = tempList;
949  }
950
951  public clearData() {
952    this.childList = []
953  }
954}
955@Component
956struct CompChild {
957  @Link childList: ChildList;
958  @ObjectLink child: Child;
959
960  build() {
961    Row() {
962      Text(this.child.count+'')
963        .height(70)
964        .fontSize(20)
965        .borderRadius({
966          topLeft: 6,
967          topRight: 6
968        })
969        .margin({left: 50})
970      Button('X')
971        .backgroundColor(Color.Red)
972        .onClick(()=>{
973          let index = this.childList.findIndex((item) => {
974            return item.count === this.child.count
975          })
976          if (index !== -1) {
977            this.childList.splice(index, 1);
978          }
979        })
980        .margin({
981          left: 200,
982          right:30
983        })
984    }
985    .margin({
986      top:15,
987      left: 15,
988      right:10,
989      bottom:15
990    })
991    .borderRadius(6)
992    .backgroundColor(Color.Grey)
993  }
994}
995@Component
996struct CompList {
997  @ObjectLink@Watch('changeChildList') childList: ChildList;
998
999  changeChildList() {
1000    console.log('CompList ChildList change');
1001  }
1002
1003  isRenderCompChild(index: number) : number {
1004    console.log("Comp Child is render" + index);
1005    return 1;
1006  }
1007
1008  build() {
1009    Column() {
1010      List() {
1011        ForEach(this.childList, (item: Child, index) => {
1012          ListItem() {
1013            CompChild({
1014              childList: this.childList,
1015              child: item
1016            })
1017              .opacity(this.isRenderCompChild(index))
1018          }
1019
1020        })
1021      }
1022      .height('70%')
1023    }
1024  }
1025}
1026@Component
1027struct CompAncestor {
1028  @ObjectLink ancestor: Ancestor;
1029
1030  build() {
1031    Column() {
1032      CompList({ childList: this.ancestor.childList })
1033      Row() {
1034        Button("Clear")
1035          .onClick(() => {
1036            this.ancestor.clearData()
1037          })
1038          .width(100)
1039          .margin({right: 50})
1040        Button("Recover")
1041          .onClick(() => {
1042            this.ancestor.loadData()
1043          })
1044          .width(100)
1045      }
1046    }
1047  }
1048}
1049@Entry
1050@Component
1051struct Page {
1052  @State childList: ChildList = [new Child(1), new Child(2), new Child(3), new Child(4),new Child(5)];
1053  @State ancestor: Ancestor = new Ancestor(this.childList)
1054
1055  build() {
1056    Column() {
1057      CompAncestor({ ancestor: this.ancestor})
1058    }
1059  }
1060}
1061```
1062
1063Below you can see how the preceding code snippet works.
1064
1065![properly-use-state-management-to-develope-6](figures/properly-use-state-management-to-develope-6.gif)
1066
1067The core of optimization is to change **tempList** of the Child[] type to an observable **ChildList** class.
1068
1069```typescript
1070public loadData() {
1071    let tempList = new ChildList();
1072    for (let i = 1; i < 6; i ++) {
1073      tempList.push(new Child(i));
1074    }
1075    this.childList = tempList;
1076  }
1077```
1078
1079In the preceding code, the ChildList type is decorated by @Observed when defined, allowing the **tempList** object created using **new** to be observed. As such, when you click **X** to delete an item, this change to **childList** is observed, the **ForEach** view updated, and the UI re-rendered.
1080
1081## Properly Using ForEach and LazyForEach
1082
1083### Minimizing the Use of LazyForEach in UI Updating
1084
1085[LazyForEach](arkts-rendering-control-lazyforeach.md) often works hand in hand with state variables.
1086
1087```typescript
1088class BasicDataSource implements IDataSource {
1089  private listeners: DataChangeListener[] = [];
1090  private originDataArray: StringData[] = [];
1091
1092  public totalCount(): number {
1093    return 0;
1094  }
1095
1096  public getData(index: number): StringData {
1097    return this.originDataArray[index];
1098  }
1099
1100  registerDataChangeListener(listener: DataChangeListener): void {
1101    if (this.listeners.indexOf(listener) < 0) {
1102      console.info('add listener');
1103      this.listeners.push(listener);
1104    }
1105  }
1106
1107  unregisterDataChangeListener(listener: DataChangeListener): void {
1108    const pos = this.listeners.indexOf(listener);
1109    if (pos >= 0) {
1110      console.info('remove listener');
1111      this.listeners.splice(pos, 1);
1112    }
1113  }
1114
1115  notifyDataReload(): void {
1116    this.listeners.forEach(listener => {
1117      listener.onDataReloaded();
1118    })
1119  }
1120
1121  notifyDataAdd(index: number): void {
1122    this.listeners.forEach(listener => {
1123      listener.onDataAdd(index);
1124    })
1125  }
1126
1127  notifyDataChange(index: number): void {
1128    this.listeners.forEach(listener => {
1129      listener.onDataChange(index);
1130    })
1131  }
1132
1133  notifyDataDelete(index: number): void {
1134    this.listeners.forEach(listener => {
1135      listener.onDataDelete(index);
1136    })
1137  }
1138
1139  notifyDataMove(from: number, to: number): void {
1140    this.listeners.forEach(listener => {
1141      listener.onDataMove(from, to);
1142    })
1143  }
1144}
1145
1146class MyDataSource extends BasicDataSource {
1147  private dataArray: StringData[] = [];
1148
1149  public totalCount(): number {
1150    return this.dataArray.length;
1151  }
1152
1153  public getData(index: number): StringData {
1154    return this.dataArray[index];
1155  }
1156
1157  public addData(index: number, data: StringData): void {
1158    this.dataArray.splice(index, 0, data);
1159    this.notifyDataAdd(index);
1160  }
1161
1162  public pushData(data: StringData): void {
1163    this.dataArray.push(data);
1164    this.notifyDataAdd(this.dataArray.length - 1);
1165  }
1166
1167  public reloadData(): void {
1168    this.notifyDataReload();
1169  }
1170}
1171
1172class StringData {
1173  message: string;
1174  imgSrc: Resource;
1175  constructor(message: string, imgSrc: Resource) {
1176    this.message = message;
1177    this.imgSrc = imgSrc;
1178  }
1179}
1180
1181@Entry
1182@Component
1183struct MyComponent {
1184  private data: MyDataSource = new MyDataSource();
1185
1186  aboutToAppear() {
1187    for (let i = 0; i <= 9; i++) {
1188      this.data.pushData(new StringData(`Click to add ${i}`, $r('app.media.icon'))); // Use app.media.app_icon since API version 12.
1189    }
1190  }
1191
1192  build() {
1193    List({ space: 3 }) {
1194      LazyForEach(this.data, (item: StringData, index: number) => {
1195        ListItem() {
1196          Column() {
1197            Text(item.message).fontSize(20)
1198              .onAppear(() => {
1199                console.info("text appear:" + item.message);
1200              })
1201            Image(item.imgSrc)
1202              .width(100)
1203              .height(100)
1204              .onAppear(() => {
1205                console.info("image appear");
1206              })
1207          }.margin({ left: 10, right: 10 })
1208        }
1209        .onClick(() => {
1210          item.message += '0';
1211          this.data.reloadData();
1212        })
1213      }, (item: StringData, index: number) => JSON.stringify(item))
1214    }.cachedCount(5)
1215  }
1216}
1217```
1218
1219Below you can see how the preceding code snippet works.
1220
1221![properly-use-state-management-to-develope-7](figures/properly-use-state-management-to-develope-7.gif)
1222
1223In this example, after you click to change **message**, the image flickers, and the onAppear log is generated for the image, indicating that the component is rebuilt. After **message** is changed, the key of the corresponding list item in **LazyForEach** changes. As a result, **LazyForEach** rebuilds the list item when executing **reloadData**. Though the **Text** component only has its content changed, it is rebuilt, not updated. The **Image** component under the list item is also rebuilt along with the list item, even though its content remains unchanged.
1224
1225While both **LazyForEach** and state variables can trigger UI re-renders, their performance overheads are different. **LazyForEach** leads to component rebuilds and higher performance overheads, especially when there is a considerable number of components. By contrast, the use of state variables allows you to keep the update scope within the closely related components. In light of this, it is recommended that you use state variables to trigger component updates in **LazyForEach**, which requires custom components.
1226
1227```typescript
1228class BasicDataSource implements IDataSource {
1229  private listeners: DataChangeListener[] = [];
1230  private originDataArray: StringData[] = [];
1231
1232  public totalCount(): number {
1233    return 0;
1234  }
1235
1236  public getData(index: number): StringData {
1237    return this.originDataArray[index];
1238  }
1239
1240  registerDataChangeListener(listener: DataChangeListener): void {
1241    if (this.listeners.indexOf(listener) < 0) {
1242      console.info('add listener');
1243      this.listeners.push(listener);
1244    }
1245  }
1246
1247  unregisterDataChangeListener(listener: DataChangeListener): void {
1248    const pos = this.listeners.indexOf(listener);
1249    if (pos >= 0) {
1250      console.info('remove listener');
1251      this.listeners.splice(pos, 1);
1252    }
1253  }
1254
1255  notifyDataReload(): void {
1256    this.listeners.forEach(listener => {
1257      listener.onDataReloaded();
1258    })
1259  }
1260
1261  notifyDataAdd(index: number): void {
1262    this.listeners.forEach(listener => {
1263      listener.onDataAdd(index);
1264    })
1265  }
1266
1267  notifyDataChange(index: number): void {
1268    this.listeners.forEach(listener => {
1269      listener.onDataChange(index);
1270    })
1271  }
1272
1273  notifyDataDelete(index: number): void {
1274    this.listeners.forEach(listener => {
1275      listener.onDataDelete(index);
1276    })
1277  }
1278
1279  notifyDataMove(from: number, to: number): void {
1280    this.listeners.forEach(listener => {
1281      listener.onDataMove(from, to);
1282    })
1283  }
1284}
1285
1286class MyDataSource extends BasicDataSource {
1287  private dataArray: StringData[] = [];
1288
1289  public totalCount(): number {
1290    return this.dataArray.length;
1291  }
1292
1293  public getData(index: number): StringData {
1294    return this.dataArray[index];
1295  }
1296
1297  public addData(index: number, data: StringData): void {
1298    this.dataArray.splice(index, 0, data);
1299    this.notifyDataAdd(index);
1300  }
1301
1302  public pushData(data: StringData): void {
1303    this.dataArray.push(data);
1304    this.notifyDataAdd(this.dataArray.length - 1);
1305  }
1306}
1307
1308@Observed
1309class StringData {
1310  @Track message: string;
1311  @Track imgSrc: Resource;
1312  constructor(message: string, imgSrc: Resource) {
1313    this.message = message;
1314    this.imgSrc = imgSrc;
1315  }
1316}
1317
1318@Entry
1319@Component
1320struct MyComponent {
1321  @State data: MyDataSource = new MyDataSource();
1322
1323  aboutToAppear() {
1324    for (let i = 0; i <= 9; i++) {
1325      this.data.pushData(new StringData(`Click to add ${i}`, $r('app.media.icon'))); // Use app.media.app_icon since API version 12.
1326    }
1327  }
1328
1329  build() {
1330    List({ space: 3 }) {
1331      LazyForEach(this.data, (item: StringData, index: number) => {
1332        ListItem() {
1333          ChildComponent({data: item})
1334        }
1335        .onClick(() => {
1336          item.message += '0';
1337        })
1338      }, (item: StringData, index: number) => index.toString())
1339    }.cachedCount(5)
1340  }
1341}
1342
1343@Component
1344struct ChildComponent {
1345  @ObjectLink data: StringData
1346  build() {
1347    Column() {
1348      Text(this.data.message).fontSize(20)
1349        .onAppear(() => {
1350          console.info("text appear:" + this.data.message)
1351        })
1352      Image(this.data.imgSrc)
1353        .width(100)
1354        .height(100)
1355    }.margin({ left: 10, right: 10 })
1356  }
1357}
1358```
1359
1360Below you can see how the preceding code snippet works.
1361
1362![properly-use-state-management-to-develope-8](figures/properly-use-state-management-to-develope-8.gif)
1363
1364In this example, the UI is re-rendered properly: The image does not flicker, and no log is generated, which indicates that the **Text** and **Image** components are not rebuilt.
1365
1366This is thanks to introduction of custom components, where state variables are directly changed through @Observed and @ObjectLink, instead of through **LazyForEach**. Decorate the **message** and **imgSrc** properties of the **StringData** type with [@Track](arkts-track.md) to further narrow down the render scope to the specified **Text** component.
1367
1368### Using Custom Components to Match Object Arrays in ForEach
1369
1370Frequently seen in applications, the combination of object arrays and [ForEach](arkts-rendering-control-foreach.md) requires special attentions. Inappropriate use may cause UI re-render issues.
1371
1372```typescript
1373@Observed
1374class StyleList extends Array<TextStyle> {
1375};
1376@Observed
1377class TextStyle {
1378  fontSize: number;
1379
1380  constructor(fontSize: number) {
1381    this.fontSize = fontSize;
1382  }
1383}
1384@Entry
1385@Component
1386struct Page {
1387  @State styleList: StyleList = new StyleList();
1388  aboutToAppear() {
1389    for (let i = 15; i < 50; i++)
1390    this.styleList.push(new TextStyle(i));
1391  }
1392  build() {
1393    Column() {
1394      Text("Font Size List")
1395        .fontSize(50)
1396        .onClick(() => {
1397          for (let i = 0; i < this.styleList.length; i++) {
1398            this.styleList[i].fontSize++;
1399          }
1400          console.log("change font size");
1401        })
1402      List() {
1403        ForEach(this.styleList, (item: TextStyle) => {
1404          ListItem() {
1405            Text("Hello World")
1406              .fontSize(item.fontSize)
1407          }
1408        })
1409      }
1410    }
1411  }
1412}
1413```
1414
1415Below you can see how the preceding code snippet works.
1416
1417![properly-use-state-management-to-develope-9](figures/properly-use-state-management-to-develope-9.gif)
1418
1419The items generated in **ForEach** are constants. This means that their value changes do not trigger UI re-renders. In this example, though an item is changed upon a click, as indicated by the "change font size" log, the UI is not updated as expected. To fix this issue, you need to use custom components with @ObjectLink.
1420
1421```typescript
1422@Observed
1423class StyleList extends Array<TextStyle> {
1424};
1425@Observed
1426class TextStyle {
1427  fontSize: number;
1428
1429  constructor(fontSize: number) {
1430    this.fontSize = fontSize;
1431  }
1432}
1433@Component
1434struct TextComponent {
1435  @ObjectLink textStyle: TextStyle;
1436  build() {
1437    Text("Hello World")
1438      .fontSize(this.textStyle.fontSize)
1439  }
1440}
1441@Entry
1442@Component
1443struct Page {
1444  @State styleList: StyleList = new StyleList();
1445  aboutToAppear() {
1446    for (let i = 15; i < 50; i++)
1447      this.styleList.push(new TextStyle(i));
1448  }
1449  build() {
1450    Column() {
1451      Text("Font Size List")
1452        .fontSize(50)
1453        .onClick(() => {
1454          for (let i = 0; i < this.styleList.length; i++) {
1455            this.styleList[i].fontSize++;
1456          }
1457          console.log("change font size");
1458        })
1459      List() {
1460        ForEach(this.styleList, (item: TextStyle) => {
1461          ListItem() {
1462            TextComponent({ textStyle: item})
1463          }
1464        })
1465      }
1466    }
1467  }
1468}
1469```
1470
1471Below you can see how the preceding code snippet works.
1472
1473![properly-use-state-management-to-develope-10](figures/properly-use-state-management-to-develope-10.gif)
1474
1475When @ObjectLink is used to accept the input item, the **textStyle** variable in the **TextComponent** component can be observed. For @ObjectLink, parameters are passed by reference. Therefore, when the value of **fontSize** in **styleList** is changed in the parent component, this update is properly observed and synced to the corresponding list item in **ForEach**, leading to UI re-rendering.
1476
1477This is a practical mode of using state management for UI re-rendering.
1478
1479
1480
1481<!--no_check-->
1482