• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                    // in low version, DevEco may throw a warning, but it does not matter.
179                    // you can still compile and run.
180                    info: info,
181                    index: index
182                  })
183                }
184                .margin({
185                  top: 5,
186                  bottom: 5
187                })
188              })
189            }
190          }
191        })
192      }
193    }
194  }
195}
196```
197
198Below you can see how the preceding code snippet works.
199
200![properly-use-state-management-to-develope-2](figures/properly-use-state-management-to-develope-2.gif)
201
202After 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).
203
204### Splitting a Complex Large Object into Multiple Small Objects
205
206> **NOTE**
207>
208> You are advised to use the [@Track](arkts-track.md) decorator in this scenario since API version 11.
209
210During 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.
211
212```typescript
213@Observed
214class UIStyle {
215  translateX: number = 0;
216  translateY: number = 0;
217  scaleX: number = 0.3;
218  scaleY: number = 0.3;
219  width: number = 336;
220  height: number = 178;
221  posX: number = 10;
222  posY: number = 50;
223  alpha: number = 0.5;
224  borderRadius: number = 24;
225  imageWidth: number = 78;
226  imageHeight: number = 78;
227  translateImageX: number = 0;
228  translateImageY: number = 0;
229  fontSize: number = 20;
230}
231@Component
232struct SpecialImage {
233  @ObjectLink uiStyle: UIStyle;
234  private isRenderSpecialImage() : number { // function to show whether the component is rendered
235    console.log("SpecialImage is rendered");
236    return 1;
237  }
238  build() {
239    Image($r('app.media.icon')) // Use app.media.app_icon since API version 12.
240      .width(this.uiStyle.imageWidth)
241      .height(this.uiStyle.imageHeight)
242      .margin({ top: 20 })
243      .translate({
244        x: this.uiStyle.translateImageX,
245        y: this.uiStyle.translateImageY
246      })
247      .opacity(this.isRenderSpecialImage()) // if the Image is rendered, it will call the function
248  }
249}
250@Component
251struct CompA {
252  @ObjectLink uiStyle: UIStyle
253  // the following functions are used to show whether the component is called to be rendered
254  private isRenderColumn() : number {
255    console.log("Column is rendered");
256    return 1;
257  }
258  private isRenderStack() : number {
259    console.log("Stack is rendered");
260    return 1;
261  }
262  private isRenderImage() : number {
263    console.log("Image is rendered");
264    return 1;
265  }
266  private isRenderText() : number {
267    console.log("Text is rendered");
268    return 1;
269  }
270  build() {
271    Column() {
272      SpecialImage({
273        // in low version, Dev Eco may throw a warning
274        // But you can still build and run the code
275        uiStyle: this.uiStyle
276      })
277      Stack() {
278        Column() {
279            Image($r('app.media.icon')) // Use app.media.app_icon since API version 12.
280              .opacity(this.uiStyle.alpha)
281              .scale({
282                x: this.uiStyle.scaleX,
283                y: this.uiStyle.scaleY
284              })
285              .padding(this.isRenderImage())
286              .width(300)
287              .height(300)
288        }
289        .width('100%')
290        .position({ y: -80 })
291        Stack() {
292          Text("Hello World")
293            .fontColor("#182431")
294            .fontWeight(FontWeight.Medium)
295            .fontSize(this.uiStyle.fontSize)
296            .opacity(this.isRenderText())
297            .margin({ top: 12 })
298        }
299        .opacity(this.isRenderStack())
300        .position({
301          x: this.uiStyle.posX,
302          y: this.uiStyle.posY
303        })
304        .width('100%')
305        .height('100%')
306      }
307      .margin({ top: 50 })
308      .borderRadius(this.uiStyle.borderRadius)
309      .opacity(this.isRenderStack())
310      .backgroundColor("#FFFFFF")
311      .width(this.uiStyle.width)
312      .height(this.uiStyle.height)
313      .translate({
314        x: this.uiStyle.translateX,
315        y: this.uiStyle.translateY
316      })
317      Column() {
318        Button("Move")
319          .width(312)
320          .fontSize(20)
321          .backgroundColor("#FF007DFF")
322          .margin({ bottom: 10 })
323          .onClick(() => {
324            animateTo({
325              duration: 500
326            },() => {
327              this.uiStyle.translateY = (this.uiStyle.translateY + 180) % 250;
328            })
329          })
330        Button("Scale")
331          .borderRadius(20)
332          .backgroundColor("#FF007DFF")
333          .fontSize(20)
334          .width(312)
335          .onClick(() => {
336            this.uiStyle.scaleX = (this.uiStyle.scaleX + 0.6) % 0.8;
337          })
338      }
339      .position({
340        y:666
341      })
342      .height('100%')
343      .width('100%')
344
345    }
346    .opacity(this.isRenderColumn())
347    .width('100%')
348    .height('100%')
349
350  }
351}
352@Entry
353@Component
354struct Page {
355  @State uiStyle: UIStyle = new UIStyle();
356  build() {
357    Stack() {
358      CompA({
359        // in low version, Dev Eco may throw a warning
360        // But you can still build and run the code
361        uiStyle: this.uiStyle
362      })
363    }
364    .backgroundColor("#F1F3F5")
365  }
366}
367```
368
369Below you can see how the preceding code snippet works.
370
371![properly-use-state-management-to-develope-3](figures/properly-use-state-management-to-develope-3.gif)
372
373Click the **Move** button before optimization. The duration for updating dirty nodes is as follows.
374
375![img](figures/properly-use-state-management-to-develope-11.PNG)
376
377In 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.
378
379Such 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.
380
381Naturally, 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.
382
383```typescript
384@Observed
385class NeedRenderImage { // properties only used in the same component can be divided into the same new divided class
386  public translateImageX: number = 0;
387  public translateImageY: number = 0;
388  public imageWidth:number = 78;
389  public imageHeight:number = 78;
390}
391@Observed
392class NeedRenderScale { // properties usually used together can be divided into the same new divided class
393  public scaleX: number = 0.3;
394  public scaleY: number = 0.3;
395}
396@Observed
397class NeedRenderAlpha { // properties that may be used in different places can be divided into the same new divided class
398  public alpha: number = 0.5;
399}
400@Observed
401class NeedRenderSize { // properties usually used together can be divided into the same new divided class
402  public width: number = 336;
403  public height: number = 178;
404}
405@Observed
406class NeedRenderPos { // properties usually used together can be divided into the same new divided class
407  public posX: number = 10;
408  public posY: number = 50;
409}
410@Observed
411class NeedRenderBorderRadius { // properties that may be used in different places can be divided into the same new divided class
412  public borderRadius: number = 24;
413}
414@Observed
415class NeedRenderFontSize { // properties that may be used in different places can be divided into the same new divided class
416  public fontSize: number = 20;
417}
418@Observed
419class NeedRenderTranslate { // properties usually used together can be divided into the same new divided class
420  public translateX: number = 0;
421  public translateY: number = 0;
422}
423@Observed
424class UIStyle {
425  // define new variable instead of using old one
426  needRenderTranslate: NeedRenderTranslate = new NeedRenderTranslate();
427  needRenderFontSize: NeedRenderFontSize = new NeedRenderFontSize();
428  needRenderBorderRadius: NeedRenderBorderRadius = new NeedRenderBorderRadius();
429  needRenderPos: NeedRenderPos = new NeedRenderPos();
430  needRenderSize: NeedRenderSize = new NeedRenderSize();
431  needRenderAlpha: NeedRenderAlpha = new NeedRenderAlpha();
432  needRenderScale: NeedRenderScale = new NeedRenderScale();
433  needRenderImage: NeedRenderImage = new NeedRenderImage();
434}
435@Component
436struct SpecialImage {
437  @ObjectLink uiStyle : UIStyle;
438  @ObjectLink needRenderImage: NeedRenderImage // receive the new class from its parent component
439  private isRenderSpecialImage() : number { // function to show whether the component is rendered
440    console.log("SpecialImage is rendered");
441    return 1;
442  }
443  build() {
444    Image($r('app.media.icon')) // Use app.media.app_icon since API version 12.
445      .width(this.needRenderImage.imageWidth) // !! use this.needRenderImage.xxx rather than this.uiStyle.needRenderImage.xxx !!
446      .height(this.needRenderImage.imageHeight)
447      .margin({top:20})
448      .translate({
449        x: this.needRenderImage.translateImageX,
450        y: this.needRenderImage.translateImageY
451      })
452      .opacity(this.isRenderSpecialImage()) // if the Image is rendered, it will call the function
453  }
454}
455@Component
456struct CompA {
457  @ObjectLink uiStyle: UIStyle;
458  @ObjectLink needRenderTranslate: NeedRenderTranslate; // receive the new class from its parent component
459  @ObjectLink needRenderFontSize: NeedRenderFontSize;
460  @ObjectLink needRenderBorderRadius: NeedRenderBorderRadius;
461  @ObjectLink needRenderPos: NeedRenderPos;
462  @ObjectLink needRenderSize: NeedRenderSize;
463  @ObjectLink needRenderAlpha: NeedRenderAlpha;
464  @ObjectLink needRenderScale: NeedRenderScale;
465  // the following functions are used to show whether the component is called to be rendered
466  private isRenderColumn() : number {
467    console.log("Column is rendered");
468    return 1;
469  }
470  private isRenderStack() : number {
471    console.log("Stack is rendered");
472    return 1;
473  }
474  private isRenderImage() : number {
475    console.log("Image is rendered");
476    return 1;
477  }
478  private isRenderText() : number {
479    console.log("Text is rendered");
480    return 1;
481  }
482  build() {
483    Column() {
484      SpecialImage({
485        // in low version, Dev Eco may throw a warning
486        // But you can still build and run the code
487        uiStyle: this.uiStyle,
488        needRenderImage: this.uiStyle.needRenderImage //send it to its child
489      })
490      Stack() {
491        Column() {
492          Image($r('app.media.icon')) // Use app.media.app_icon since API version 12.
493            .opacity(this.needRenderAlpha.alpha)
494            .scale({
495              x: this.needRenderScale.scaleX, // use this.needRenderXxx.xxx rather than this.uiStyle.needRenderXxx.xxx
496              y: this.needRenderScale.scaleY
497            })
498            .padding(this.isRenderImage())
499            .width(300)
500            .height(300)
501        }
502        .width('100%')
503        .position({ y: -80 })
504
505        Stack() {
506          Text("Hello World")
507            .fontColor("#182431")
508            .fontWeight(FontWeight.Medium)
509            .fontSize(this.needRenderFontSize.fontSize)
510            .opacity(this.isRenderText())
511            .margin({ top: 12 })
512        }
513        .opacity(this.isRenderStack())
514        .position({
515          x: this.needRenderPos.posX,
516          y: this.needRenderPos.posY
517        })
518        .width('100%')
519        .height('100%')
520      }
521      .margin({ top: 50 })
522      .borderRadius(this.needRenderBorderRadius.borderRadius)
523      .opacity(this.isRenderStack())
524      .backgroundColor("#FFFFFF")
525      .width(this.needRenderSize.width)
526      .height(this.needRenderSize.height)
527      .translate({
528        x: this.needRenderTranslate.translateX,
529        y: this.needRenderTranslate.translateY
530      })
531
532      Column() {
533        Button("Move")
534          .width(312)
535          .fontSize(20)
536          .backgroundColor("#FF007DFF")
537          .margin({ bottom: 10 })
538          .onClick(() => {
539            animateTo({
540              duration: 500
541            }, () => {
542              this.needRenderTranslate.translateY = (this.needRenderTranslate.translateY + 180) % 250;
543            })
544          })
545        Button("Scale")
546          .borderRadius(20)
547          .backgroundColor("#FF007DFF")
548          .fontSize(20)
549          .width(312)
550          .margin({ bottom: 10 })
551          .onClick(() => {
552            this.needRenderScale.scaleX = (this.needRenderScale.scaleX + 0.6) % 0.8;
553          })
554        Button("Change Image")
555          .borderRadius(20)
556          .backgroundColor("#FF007DFF")
557          .fontSize(20)
558          .width(312)
559          .onClick(() => { // in the parent component, still use this.uiStyle.needRenderXxx.xxx to change the properties
560            this.uiStyle.needRenderImage.imageWidth = (this.uiStyle.needRenderImage.imageWidth + 30) % 160;
561            this.uiStyle.needRenderImage.imageHeight = (this.uiStyle.needRenderImage.imageHeight + 30) % 160;
562          })
563      }
564      .position({
565        y: 616
566      })
567      .height('100%')
568      .width('100%')
569    }
570    .opacity(this.isRenderColumn())
571    .width('100%')
572    .height('100%')
573  }
574}
575@Entry
576@Component
577struct Page {
578  @State uiStyle: UIStyle = new UIStyle();
579  build() {
580    Stack() {
581      CompA({
582        // in low version, Dev Eco may throw a warning
583        // But you can still build and run the code
584        uiStyle: this.uiStyle,
585        needRenderTranslate: this.uiStyle.needRenderTranslate, //send all the new class child need
586        needRenderFontSize: this.uiStyle.needRenderFontSize,
587        needRenderBorderRadius: this.uiStyle.needRenderBorderRadius,
588        needRenderPos: this.uiStyle.needRenderPos,
589        needRenderSize: this.uiStyle.needRenderSize,
590        needRenderAlpha: this.uiStyle.needRenderAlpha,
591        needRenderScale: this.uiStyle.needRenderScale
592      })
593    }
594    .backgroundColor("#F1F3F5")
595  }
596}
597```
598
599Below 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)
600
601Click the **Move** button after optimization. The duration for updating dirty nodes is as follows.
602
603![img](figures/properly-use-state-management-to-develope-12.PNG)
604
605After 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:
606
607- 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.
608- 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).
609- 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).
610
611As 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-->
612
613@Track decorator can also precisely control the render scope, which does not involve division of properties.
614
615```ts
616@Observed
617class UIStyle {
618  @Track translateX: number = 0;
619  @Track translateY: number = 0;
620  @Track scaleX: number = 0.3;
621  @Track scaleY: number = 0.3;
622  @Track width: number = 336;
623  @Track height: number = 178;
624  @Track posX: number = 10;
625  @Track posY: number = 50;
626  @Track alpha: number = 0.5;
627  @Track borderRadius: number = 24;
628  @Track imageWidth: number = 78;
629  @Track imageHeight: number = 78;
630  @Track translateImageX: number = 0;
631  @Track translateImageY: number = 0;
632  @Track fontSize: number = 20;
633}
634@Component
635struct SpecialImage {
636  @ObjectLink uiStyle: UIStyle;
637  private isRenderSpecialImage() : number { // function to show whether the component is rendered
638    console.log("SpecialImage is rendered");
639    return 1;
640  }
641  build() {
642    Image($r('app.media.icon')) // Use app.media.app_icon since API version 12.
643      .width(this.uiStyle.imageWidth)
644      .height(this.uiStyle.imageHeight)
645      .margin({ top: 20 })
646      .translate({
647        x: this.uiStyle.translateImageX,
648        y: this.uiStyle.translateImageY
649      })
650      .opacity(this.isRenderSpecialImage()) // if the Image is rendered, it will call the function
651  }
652}
653@Component
654struct CompA {
655  @ObjectLink uiStyle: UIStyle
656  // the following functions are used to show whether the component is called to be rendered
657  private isRenderColumn() : number {
658    console.log("Column is rendered");
659    return 1;
660  }
661  private isRenderStack() : number {
662    console.log("Stack is rendered");
663    return 1;
664  }
665  private isRenderImage() : number {
666    console.log("Image is rendered");
667    return 1;
668  }
669  private isRenderText() : number {
670    console.log("Text is rendered");
671    return 1;
672  }
673  build() {
674    Column() {
675      SpecialImage({
676        // in low version, Dev Eco may throw a warning
677        // But you can still build and run the code
678        uiStyle: this.uiStyle
679      })
680      Stack() {
681        Column() {
682            Image($r('app.media.icon')) // Use app.media.app_icon since API version 12.
683              .opacity(this.uiStyle.alpha)
684              .scale({
685                x: this.uiStyle.scaleX,
686                y: this.uiStyle.scaleY
687              })
688              .padding(this.isRenderImage())
689              .width(300)
690              .height(300)
691        }
692        .width('100%')
693        .position({ y: -80 })
694        Stack() {
695          Text("Hello World")
696            .fontColor("#182431")
697            .fontWeight(FontWeight.Medium)
698            .fontSize(this.uiStyle.fontSize)
699            .opacity(this.isRenderText())
700            .margin({ top: 12 })
701        }
702        .opacity(this.isRenderStack())
703        .position({
704          x: this.uiStyle.posX,
705          y: this.uiStyle.posY
706        })
707        .width('100%')
708        .height('100%')
709      }
710      .margin({ top: 50 })
711      .borderRadius(this.uiStyle.borderRadius)
712      .opacity(this.isRenderStack())
713      .backgroundColor("#FFFFFF")
714      .width(this.uiStyle.width)
715      .height(this.uiStyle.height)
716      .translate({
717        x: this.uiStyle.translateX,
718        y: this.uiStyle.translateY
719      })
720      Column() {
721        Button("Move")
722          .width(312)
723          .fontSize(20)
724          .backgroundColor("#FF007DFF")
725          .margin({ bottom: 10 })
726          .onClick(() => {
727            animateTo({
728              duration: 500
729            },() => {
730              this.uiStyle.translateY = (this.uiStyle.translateY + 180) % 250;
731            })
732          })
733        Button("Scale")
734          .borderRadius(20)
735          .backgroundColor("#FF007DFF")
736          .fontSize(20)
737          .width(312)
738          .onClick(() => {
739            this.uiStyle.scaleX = (this.uiStyle.scaleX + 0.6) % 0.8;
740          })
741      }
742      .position({
743        y:666
744      })
745      .height('100%')
746      .width('100%')
747
748    }
749    .opacity(this.isRenderColumn())
750    .width('100%')
751    .height('100%')
752
753  }
754}
755@Entry
756@Component
757struct Page {
758  @State uiStyle: UIStyle = new UIStyle();
759  build() {
760    Stack() {
761      CompA({
762        // in low version, Dev Eco may throw a warning
763        // But you can still build and run the code
764        uiStyle: this.uiStyle
765      })
766    }
767    .backgroundColor("#F1F3F5")
768  }
769}
770```
771
772### Binding Components to Class Objects Decorated with @Observed or Declared as State Variables
773
774Your 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.
775
776```typescript
777@Observed
778class Child {
779  count: number;
780  constructor(count: number) {
781    this.count = count
782  }
783}
784@Observed
785class ChildList extends Array<Child> {
786};
787@Observed
788class Ancestor {
789  childList: ChildList;
790  constructor(childList: ChildList) {
791    this.childList = childList;
792  }
793  public loadData() {
794    let tempList = [new Child(1), new Child(2), new Child(3), new Child(4), new Child(5)];
795    this.childList = tempList;
796  }
797
798  public clearData() {
799    this.childList = []
800  }
801}
802@Component
803struct CompChild {
804  @Link childList: ChildList;
805  @ObjectLink child: Child;
806
807  build() {
808    Row() {
809      Text(this.child.count+'')
810        .height(70)
811        .fontSize(20)
812        .borderRadius({
813          topLeft: 6,
814          topRight: 6
815        })
816        .margin({left: 50})
817      Button('X')
818        .backgroundColor(Color.Red)
819        .onClick(()=>{
820          let index = this.childList.findIndex((item) => {
821            return item.count === this.child.count
822          })
823          if (index !== -1) {
824            this.childList.splice(index, 1);
825          }
826        })
827        .margin({
828          left: 200,
829          right:30
830        })
831    }
832    .margin({
833      top:15,
834      left: 15,
835      right:10,
836      bottom:15
837    })
838    .borderRadius(6)
839    .backgroundColor(Color.Grey)
840  }
841}
842@Component
843struct CompList {
844  @ObjectLink@Watch('changeChildList') childList: ChildList;
845
846  changeChildList() {
847    console.log('CompList ChildList change');
848  }
849
850  isRenderCompChild(index: number) : number {
851    console.log("Comp Child is render" + index);
852    return 1;
853  }
854
855  build() {
856    Column() {
857      List() {
858        ForEach(this.childList, (item: Child, index) => {
859          ListItem() {
860            // in low version, Dev Eco may throw a warning
861            // But you can still build and run the code
862            CompChild({
863              childList: this.childList,
864              child: item
865            })
866              .opacity(this.isRenderCompChild(index))
867          }
868
869        })
870      }
871      .height('70%')
872    }
873  }
874}
875@Component
876struct CompAncestor {
877  @ObjectLink ancestor: Ancestor;
878
879  build() {
880    Column() {
881      // in low version, Dev Eco may throw a warning
882      // But you can still build and run the code
883      CompList({ childList: this.ancestor.childList })
884      Row() {
885        Button("Clear")
886          .onClick(() => {
887            this.ancestor.clearData()
888          })
889          .width(100)
890          .margin({right: 50})
891        Button("Recover")
892          .onClick(() => {
893            this.ancestor.loadData()
894          })
895          .width(100)
896      }
897    }
898  }
899}
900@Entry
901@Component
902struct Page {
903  @State childList: ChildList = [new Child(1), new Child(2), new Child(3), new Child(4),new Child(5)];
904  @State ancestor: Ancestor = new Ancestor(this.childList)
905
906  build() {
907    Column() {
908      // in low version, Dev Eco may throw a warning
909      // But you can still build and run the code
910      CompAncestor({ ancestor: this.ancestor})
911    }
912  }
913}
914```
915
916Below you can see how the preceding code snippet works.
917
918![properly-use-state-management-to-develope-5](figures/properly-use-state-management-to-develope-5.gif)
919
920In 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.
921
922An 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.
923
924```typescript
925  public loadData() {
926    let tempList = [new Child(1), new Child(2), new Child(3), new Child(4), new Child(5)];
927    this.childList = tempList;
928  }
929```
930
931In 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**.
932
933You may notice that **childList** is initialized in the same way when it is defined in **Page**.
934
935```typescript
936@State childList: ChildList = [new Child(1), new Child(2), new Child(3), new Child(4),new Child(5)];
937@State ancestor: Ancestor = new Ancestor(this.childList)
938```
939
940Yet, **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.
941
942In summary, for the UI to be re-rendered properly upon value changes of class objects, these class objects must be observable.
943
944```typescript
945@Observed
946class Child {
947  count: number;
948  constructor(count: number) {
949    this.count = count
950  }
951}
952@Observed
953class ChildList extends Array<Child> {
954};
955@Observed
956class Ancestor {
957  childList: ChildList;
958  constructor(childList: ChildList) {
959    this.childList = childList;
960  }
961  public loadData() {
962    let tempList = new ChildList();
963    for (let i = 1; i < 6; i ++) {
964      tempList.push(new Child(i));
965    }
966    this.childList = tempList;
967  }
968
969  public clearData() {
970    this.childList = []
971  }
972}
973@Component
974struct CompChild {
975  @Link childList: ChildList;
976  @ObjectLink child: Child;
977
978  build() {
979    Row() {
980      Text(this.child.count+'')
981        .height(70)
982        .fontSize(20)
983        .borderRadius({
984          topLeft: 6,
985          topRight: 6
986        })
987        .margin({left: 50})
988      Button('X')
989        .backgroundColor(Color.Red)
990        .onClick(()=>{
991          let index = this.childList.findIndex((item) => {
992            return item.count === this.child.count
993          })
994          if (index !== -1) {
995            this.childList.splice(index, 1);
996          }
997        })
998        .margin({
999          left: 200,
1000          right:30
1001        })
1002    }
1003    .margin({
1004      top:15,
1005      left: 15,
1006      right:10,
1007      bottom:15
1008    })
1009    .borderRadius(6)
1010    .backgroundColor(Color.Grey)
1011  }
1012}
1013@Component
1014struct CompList {
1015  @ObjectLink@Watch('changeChildList') childList: ChildList;
1016
1017  changeChildList() {
1018    console.log('CompList ChildList change');
1019  }
1020
1021  isRenderCompChild(index: number) : number {
1022    console.log("Comp Child is render" + index);
1023    return 1;
1024  }
1025
1026  build() {
1027    Column() {
1028      List() {
1029        ForEach(this.childList, (item: Child, index) => {
1030          ListItem() {
1031            // in low version, Dev Eco may throw a warning
1032            // But you can still build and run the code
1033            CompChild({
1034              childList: this.childList,
1035              child: item
1036            })
1037              .opacity(this.isRenderCompChild(index))
1038          }
1039
1040        })
1041      }
1042      .height('70%')
1043    }
1044  }
1045}
1046@Component
1047struct CompAncestor {
1048  @ObjectLink ancestor: Ancestor;
1049
1050  build() {
1051    Column() {
1052      // in low version, Dev Eco may throw a warning
1053      // But you can still build and run the code
1054      CompList({ childList: this.ancestor.childList })
1055      Row() {
1056        Button("Clear")
1057          .onClick(() => {
1058            this.ancestor.clearData()
1059          })
1060          .width(100)
1061          .margin({right: 50})
1062        Button("Recover")
1063          .onClick(() => {
1064            this.ancestor.loadData()
1065          })
1066          .width(100)
1067      }
1068    }
1069  }
1070}
1071@Entry
1072@Component
1073struct Page {
1074  @State childList: ChildList = [new Child(1), new Child(2), new Child(3), new Child(4),new Child(5)];
1075  @State ancestor: Ancestor = new Ancestor(this.childList)
1076
1077  build() {
1078    Column() {
1079      // in low version, Dev Eco may throw a warning
1080      // But you can still build and run the code
1081      CompAncestor({ ancestor: this.ancestor})
1082    }
1083  }
1084}
1085```
1086
1087Below you can see how the preceding code snippet works.
1088
1089![properly-use-state-management-to-develope-6](figures/properly-use-state-management-to-develope-6.gif)
1090
1091The core of optimization is to change **tempList** of the Child[] type to an observable **ChildList** class.
1092
1093```typescript
1094public loadData() {
1095    let tempList = new ChildList();
1096    for (let i = 1; i < 6; i ++) {
1097      tempList.push(new Child(i));
1098    }
1099    this.childList = tempList;
1100  }
1101```
1102
1103In 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.
1104
1105## Properly Using ForEach and LazyForEach
1106
1107### Minimizing the Use of LazyForEach in UI Updating
1108
1109[LazyForEach](arkts-rendering-control-lazyforeach.md) often works hand in hand with state variables.
1110
1111```typescript
1112class BasicDataSource implements IDataSource {
1113  private listeners: DataChangeListener[] = [];
1114  private originDataArray: StringData[] = [];
1115
1116  public totalCount(): number {
1117    return 0;
1118  }
1119
1120  public getData(index: number): StringData {
1121    return this.originDataArray[index];
1122  }
1123
1124  registerDataChangeListener(listener: DataChangeListener): void {
1125    if (this.listeners.indexOf(listener) < 0) {
1126      console.info('add listener');
1127      this.listeners.push(listener);
1128    }
1129  }
1130
1131  unregisterDataChangeListener(listener: DataChangeListener): void {
1132    const pos = this.listeners.indexOf(listener);
1133    if (pos >= 0) {
1134      console.info('remove listener');
1135      this.listeners.splice(pos, 1);
1136    }
1137  }
1138
1139  notifyDataReload(): void {
1140    this.listeners.forEach(listener => {
1141      listener.onDataReloaded();
1142    })
1143  }
1144
1145  notifyDataAdd(index: number): void {
1146    this.listeners.forEach(listener => {
1147      listener.onDataAdd(index);
1148    })
1149  }
1150
1151  notifyDataChange(index: number): void {
1152    this.listeners.forEach(listener => {
1153      listener.onDataChange(index);
1154    })
1155  }
1156
1157  notifyDataDelete(index: number): void {
1158    this.listeners.forEach(listener => {
1159      listener.onDataDelete(index);
1160    })
1161  }
1162
1163  notifyDataMove(from: number, to: number): void {
1164    this.listeners.forEach(listener => {
1165      listener.onDataMove(from, to);
1166    })
1167  }
1168}
1169
1170class MyDataSource extends BasicDataSource {
1171  private dataArray: StringData[] = [];
1172
1173  public totalCount(): number {
1174    return this.dataArray.length;
1175  }
1176
1177  public getData(index: number): StringData {
1178    return this.dataArray[index];
1179  }
1180
1181  public addData(index: number, data: StringData): void {
1182    this.dataArray.splice(index, 0, data);
1183    this.notifyDataAdd(index);
1184  }
1185
1186  public pushData(data: StringData): void {
1187    this.dataArray.push(data);
1188    this.notifyDataAdd(this.dataArray.length - 1);
1189  }
1190
1191  public reloadData(): void {
1192    this.notifyDataReload();
1193  }
1194}
1195
1196class StringData {
1197  message: string;
1198  imgSrc: Resource;
1199  constructor(message: string, imgSrc: Resource) {
1200    this.message = message;
1201    this.imgSrc = imgSrc;
1202  }
1203}
1204
1205@Entry
1206@Component
1207struct MyComponent {
1208  private data: MyDataSource = new MyDataSource();
1209
1210  aboutToAppear() {
1211    for (let i = 0; i <= 9; i++) {
1212      this.data.pushData(new StringData(`Click to add ${i}`, $r('app.media.icon'))); // Use app.media.app_icon since API version 12.
1213    }
1214  }
1215
1216  build() {
1217    List({ space: 3 }) {
1218      LazyForEach(this.data, (item: StringData, index: number) => {
1219        ListItem() {
1220          Column() {
1221            Text(item.message).fontSize(20)
1222              .onAppear(() => {
1223                console.info("text appear:" + item.message);
1224              })
1225            Image(item.imgSrc)
1226              .width(100)
1227              .height(100)
1228              .onAppear(() => {
1229                console.info("image appear");
1230              })
1231          }.margin({ left: 10, right: 10 })
1232        }
1233        .onClick(() => {
1234          item.message += '0';
1235          this.data.reloadData();
1236        })
1237      }, (item: StringData, index: number) => JSON.stringify(item))
1238    }.cachedCount(5)
1239  }
1240}
1241```
1242
1243Below you can see how the preceding code snippet works.
1244
1245![properly-use-state-management-to-develope-7](figures/properly-use-state-management-to-develope-7.gif)
1246
1247In 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.
1248
1249While 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.
1250
1251```typescript
1252class BasicDataSource implements IDataSource {
1253  private listeners: DataChangeListener[] = [];
1254  private originDataArray: StringData[] = [];
1255
1256  public totalCount(): number {
1257    return 0;
1258  }
1259
1260  public getData(index: number): StringData {
1261    return this.originDataArray[index];
1262  }
1263
1264  registerDataChangeListener(listener: DataChangeListener): void {
1265    if (this.listeners.indexOf(listener) < 0) {
1266      console.info('add listener');
1267      this.listeners.push(listener);
1268    }
1269  }
1270
1271  unregisterDataChangeListener(listener: DataChangeListener): void {
1272    const pos = this.listeners.indexOf(listener);
1273    if (pos >= 0) {
1274      console.info('remove listener');
1275      this.listeners.splice(pos, 1);
1276    }
1277  }
1278
1279  notifyDataReload(): void {
1280    this.listeners.forEach(listener => {
1281      listener.onDataReloaded();
1282    })
1283  }
1284
1285  notifyDataAdd(index: number): void {
1286    this.listeners.forEach(listener => {
1287      listener.onDataAdd(index);
1288    })
1289  }
1290
1291  notifyDataChange(index: number): void {
1292    this.listeners.forEach(listener => {
1293      listener.onDataChange(index);
1294    })
1295  }
1296
1297  notifyDataDelete(index: number): void {
1298    this.listeners.forEach(listener => {
1299      listener.onDataDelete(index);
1300    })
1301  }
1302
1303  notifyDataMove(from: number, to: number): void {
1304    this.listeners.forEach(listener => {
1305      listener.onDataMove(from, to);
1306    })
1307  }
1308}
1309
1310class MyDataSource extends BasicDataSource {
1311  private dataArray: StringData[] = [];
1312
1313  public totalCount(): number {
1314    return this.dataArray.length;
1315  }
1316
1317  public getData(index: number): StringData {
1318    return this.dataArray[index];
1319  }
1320
1321  public addData(index: number, data: StringData): void {
1322    this.dataArray.splice(index, 0, data);
1323    this.notifyDataAdd(index);
1324  }
1325
1326  public pushData(data: StringData): void {
1327    this.dataArray.push(data);
1328    this.notifyDataAdd(this.dataArray.length - 1);
1329  }
1330}
1331
1332@Observed
1333class StringData {
1334  @Track message: string;
1335  @Track imgSrc: Resource;
1336  constructor(message: string, imgSrc: Resource) {
1337    this.message = message;
1338    this.imgSrc = imgSrc;
1339  }
1340}
1341
1342@Entry
1343@Component
1344struct MyComponent {
1345  @State data: MyDataSource = new MyDataSource();
1346
1347  aboutToAppear() {
1348    for (let i = 0; i <= 9; i++) {
1349      this.data.pushData(new StringData(`Click to add ${i}`, $r('app.media.icon'))); // Use app.media.app_icon since API version 12.
1350    }
1351  }
1352
1353  build() {
1354    List({ space: 3 }) {
1355      LazyForEach(this.data, (item: StringData, index: number) => {
1356        ListItem() {
1357          // in low version, Dev Eco may throw a warning
1358          // But you can still build and run the code
1359          ChildComponent({data: item})
1360        }
1361        .onClick(() => {
1362          item.message += '0';
1363        })
1364      }, (item: StringData, index: number) => index.toString())
1365    }.cachedCount(5)
1366  }
1367}
1368
1369@Component
1370struct ChildComponent {
1371  @ObjectLink data: StringData
1372  build() {
1373    Column() {
1374      Text(this.data.message).fontSize(20)
1375        .onAppear(() => {
1376          console.info("text appear:" + this.data.message)
1377        })
1378      Image(this.data.imgSrc)
1379        .width(100)
1380        .height(100)
1381    }.margin({ left: 10, right: 10 })
1382  }
1383}
1384```
1385
1386Below you can see how the preceding code snippet works.
1387
1388![properly-use-state-management-to-develope-8](figures/properly-use-state-management-to-develope-8.gif)
1389
1390In 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.
1391
1392This 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.
1393
1394### Using Custom Components to Match Object Arrays in ForEach
1395
1396Frequently 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.
1397
1398```typescript
1399@Observed
1400class StyleList extends Array<TextStyle> {
1401};
1402@Observed
1403class TextStyle {
1404  fontSize: number;
1405
1406  constructor(fontSize: number) {
1407    this.fontSize = fontSize;
1408  }
1409}
1410@Entry
1411@Component
1412struct Page {
1413  @State styleList: StyleList = new StyleList();
1414  aboutToAppear() {
1415    for (let i = 15; i < 50; i++)
1416    this.styleList.push(new TextStyle(i));
1417  }
1418  build() {
1419    Column() {
1420      Text("Font Size List")
1421        .fontSize(50)
1422        .onClick(() => {
1423          for (let i = 0; i < this.styleList.length; i++) {
1424            this.styleList[i].fontSize++;
1425          }
1426          console.log("change font size");
1427        })
1428      List() {
1429        ForEach(this.styleList, (item: TextStyle) => {
1430          ListItem() {
1431            Text("Hello World")
1432              .fontSize(item.fontSize)
1433          }
1434        })
1435      }
1436    }
1437  }
1438}
1439```
1440
1441Below you can see how the preceding code snippet works.
1442
1443![properly-use-state-management-to-develope-9](figures/properly-use-state-management-to-develope-9.gif)
1444
1445The 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.
1446
1447```typescript
1448@Observed
1449class StyleList extends Array<TextStyle> {
1450};
1451@Observed
1452class TextStyle {
1453  fontSize: number;
1454
1455  constructor(fontSize: number) {
1456    this.fontSize = fontSize;
1457  }
1458}
1459@Component
1460struct TextComponent {
1461  @ObjectLink textStyle: TextStyle;
1462  build() {
1463    Text("Hello World")
1464      .fontSize(this.textStyle.fontSize)
1465  }
1466}
1467@Entry
1468@Component
1469struct Page {
1470  @State styleList: StyleList = new StyleList();
1471  aboutToAppear() {
1472    for (let i = 15; i < 50; i++)
1473      this.styleList.push(new TextStyle(i));
1474  }
1475  build() {
1476    Column() {
1477      Text("Font Size List")
1478        .fontSize(50)
1479        .onClick(() => {
1480          for (let i = 0; i < this.styleList.length; i++) {
1481            this.styleList[i].fontSize++;
1482          }
1483          console.log("change font size");
1484        })
1485      List() {
1486        ForEach(this.styleList, (item: TextStyle) => {
1487          ListItem() {
1488            // in low version, Dev Eco may throw a warning
1489            // But you can still build and run the code
1490            TextComponent({ textStyle: item})
1491          }
1492        })
1493      }
1494    }
1495  }
1496}
1497```
1498
1499Below you can see how the preceding code snippet works.
1500
1501![properly-use-state-management-to-develope-10](figures/properly-use-state-management-to-develope-10.gif)
1502
1503When @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.
1504
1505This is a practical mode of using state management for UI re-rendering.
1506
1507<!--no_check-->
1508