1# Best Practices for State Management
2
3
4This guide outlines best practices for state management in ArkUI applications. Read on to discover the common pitfalls in state management and how to avoid them, with carefully selected examples of recommended and not-recommended practices.
5
6## Replacing @Prop with @ObjectLink to Minimize Unnecessary Deep Copy
7
8When you need to pass values between parent and child components, choosing the right decorator can significantly improve application performance. If the value of a state variable is not changed in the child component, using @Prop to decorate the state variable will mean more time required in component creation.
9
10[Incorrect Usage]
11
12```ts
13@Observed
14class ClassA {
15  public c: number = 0;
16
17  constructor(c: number) {
18    this.c = c;
19  }
20}
21
22@Component
23struct PropChild {
24  The @Prop testNum: ClassA; // @Prop makes a deep copy.
25
26  build() {
27    Text(`PropChild testNum ${this.testNum.c}`)
28  }
29}
30
31@Entry
32@Component
33struct Parent {
34  @State testNum: ClassA[] = [new ClassA(1)];
35
36  build() {
37    Column() {
38      Text(`Parent testNum ${this.testNum[0].c}`)
39        .onClick(() => {
40          this.testNum[0].c += 1;
41        })
42
43      // PropChild does not change the value of @Prop testNum: ClassA. Therefore, @ObjectLink is a better choice.
44      PropChild({ testNum: this.testNum[0] })
45    }
46  }
47}
48```
49
50In the preceding example, the **PropChild** component does not change the value of **\@Prop testNum: ClassA**. In this case, \@ObjectLink is a better choice, because \@Prop makes a deep copy and increases performance overhead.
51
52[Correct Usage]
53
54```ts
55@Observed
56class ClassA {
57  public c: number = 0;
58
59  constructor(c: number) {
60    this.c = c;
61  }
62}
63
64@Component
65struct PropChild {
66  @ObjectLink testNum: ClassA; // @ObjectLink does not make a deep copy.
67
68  build() {
69    Text(`PropChild testNum ${this.testNum.c}`)
70  }
71}
72
73@Entry
74@Component
75struct Parent {
76  @State testNum: ClassA[] = [new ClassA(1)];
77
78  build() {
79    Column() {
80      Text(`Parent testNum ${this.testNum[0].c}`)
81        .onClick(() => {
82          this.testNum[0].c += 1;
83        })
84
85      // When a child component does not need to be changed locally, @ObjectLink is preferred over @Prop, whose deep copy can result in an increase in overhead.
86      PropChild({ testNum: this.testNum[0] })
87    }
88  }
89}
90```
91
92
93## Avoiding Forcibly Updating Unassociated Components Through State Variables
94
95[Incorrect Usage]
96
97
98```ts
99@Entry
100@Component
101struct CompA {
102  @State needsUpdate: boolean = true;
103  realState1: Array<number> = [4, 1, 3, 2]; // No state variable decorator is used.
104  realState2: Color = Color.Yellow;
105
106  updateUI1(param: Array<number>): Array<number> {
107    const triggerAGet = this.needsUpdate;
108    return param;
109  }
110  updateUI2(param: Color): Color {
111    const triggerAGet = this.needsUpdate;
112    return param;
113  }
114  build() {
115    Column({ space: 20 }) {
116      ForEach(this.updateUI1(this.realState1),
117        (item: Array<number>) => {
118          Text(`${item}`)
119        })
120      Text("add item")
121        .onClick(() => {
122          // Changing realState1 does not trigger UI update.
123          this.realState1.push(this.realState1[this.realState1.length-1] + 1);
124
125          // Trigger the UI update.
126          this.needsUpdate = !this.needsUpdate;
127        })
128      Text("chg color")
129        .onClick(() => {
130          // Changing realState2 does not trigger UI update.
131          this.realState2 = this.realState2 == Color.Yellow ? Color.Red : Color.Yellow;
132
133          // Trigger the UI update.
134          this.needsUpdate = !this.needsUpdate;
135        })
136    }.backgroundColor(this.updateUI2(this.realState2))
137    .width(200).height(500)
138  }
139}
140```
141
142The preceding example has the following pitfalls:
143
144- The application wants to control the UI update logic, but in ArkUI, the UI update logic should be implemented by the framework detecting changes to the application state variables.
145
146- **this.needsUpdate** is a custom state variable that should be applied only to the UI component to which it is bound. Because **this.realState1** and **this.realState2** are regular variables (not decorated), their changes do not trigger UI re-render.
147
148- However, in this application, an attempt is made to update these two regular variables through **this.needsUpdate**. This approach is nonviable and may result in poor re-render performance.
149
150[Correct Usage]
151
152To address this issue, decorate the **realState1** and **realState2** variables with \@State. Then, the variable **needsUpdate** is no longer required.
153
154
155```ts
156@Entry
157@Component
158struct CompA {
159  @State realState1: Array<number> = [4, 1, 3, 2];
160  @State realState2: Color = Color.Yellow;
161  build() {
162    Column({ space: 20 }) {
163      ForEach(this.realState1,
164        (item: Array<number>) => {
165          Text(`${item}`)
166        })
167      Text("add item")
168        .onClick(() => {
169          // Changing realState1 triggers UI update.
170          this.realState1.push(this.realState1[this.realState1.length-1] + 1);
171        })
172      Text("chg color")
173        .onClick(() => {
174          // Changing realState2 triggers UI update.
175          this.realState2 = this.realState2 == Color.Yellow ? Color.Red : Color.Yellow;
176        })
177    }.backgroundColor(this.realState2)
178    .width(200).height(500)
179  }
180}
181```
182
183## Precisely Controlling the Number of Components Associated with State Variables
184
185It is recommended that the number of components associated with each state variable be less than 20. When components are associated with a state variable, they are re-rendered when the state value changes. The more components associated, the more components re-rendered, and the heavier the UI thread load, which causes a drop in application performance. Things can get worse when the associated components are complex. Therefore, it is critical to precisely control the number of associated components. For example, instead of associating a state variable with multiple components at the same level, associating it with these components' parent can greatly reduce the number of components to be re-rendered, thereby improving UI responsiveness.
186
187[Incorrect Usage]
188
189```ts
190@Observed
191class Translate {
192  translateX: number = 20;
193}
194@Component
195struct Title {
196  @ObjectLink translateObj: Translate;
197  build() {
198    Row() {
199      Image($r('app.media.icon'))
200        .width(50)
201        .height(50)
202        .translate({
203          x:this.translateObj.translateX // this.translateObj.translateX is bound to the Image and Text components.
204        })
205      Text("Title")
206        .fontSize(20)
207        .translate({
208          x: this.translateObj.translateX
209        })
210    }
211  }
212}
213@Entry
214@Component
215struct Page {
216  @State translateObj: Translate = new Translate();
217  build() {
218    Column() {
219      Title({
220        translateObj: this.translateObj
221      })
222      Stack() {
223      }
224      .backgroundColor("black")
225      .width(200)
226      .height(400)
227      .translate({
228        x:this.translateObj.translateX // this.translateObj.translateX is bound to the Stack and Button components.
229      })
230      Button("move")
231        .translate({
232          x:this.translateObj.translateX
233        })
234        .onClick(() => {
235          animateTo({
236            duration: 50
237          },()=>{
238            this.translateObj.translateX = (this.translateObj.translateX + 50) % 150
239          })
240        })
241    }
242  }
243}
244```
245
246In the preceding example, the state variable **this.translateObj.translateX** is used in multiple child components at the same level. When it changes, all these associated components are re-rendered. Since the changes of these components are the same, you can associate the state variable with their parent component to reduce the number of components re-rendered. Analysis reveals that all these child components are located in the **Column** component under struct **Page**. Therefore, you can associate the **translate** attribute to the **Column** component instead.
247
248[Correct Usage]
249
250```ts
251@Observed
252class Translate {
253  translateX: number = 20;
254}
255@Component
256struct Title {
257  build() {
258    Row() {
259      Image($r('app.media.icon'))
260        .width(50)
261        .height(50)
262      Text("Title")
263        .fontSize(20)
264    }
265  }
266}
267@Entry
268@Component
269struct Page1 {
270  @State translateObj: Translate = new Translate();
271  build() {
272    Column() {
273      Title()
274      Stack() {
275      }
276      .backgroundColor("black")
277      .width(200)
278      .height(400)
279      Button("move")
280        .onClick(() => {
281          animateTo({
282            duration: 50
283          },()=>{
284            this.translateObj.translateX = (this.translateObj.translateX + 50) % 150
285          })
286        })
287    }
288    .translate({ // The same translate attribute is set for both the Stack and Button child components on the Column layer.
289      x: this.translateObj.translateX
290    })
291  }
292}
293```
294
295## Properly Controlling the Number of Components Associated with Object State Variables
296
297
298When a complex object is defined as a state variable, take care to control the number of components associated with the object - a change to any property of the object will cause a re-render of these components, even when they do not directly use the changed property. To reduce redundant re-renders and help deliver a smooth experience, split the complex object as appropriate and control the number of components associated with the object. For details, see [Precisely Controlling Render Scope](https://gitee.com/openharmony/docs/blob/master/en/application-dev/performance/precisely-control-render-scope.md) and [Proper Use of State Management](https://gitee.com/openharmony/docs/blob/master/en/application-dev/quick-start/properly-use-state-management-to-develope.md).
299
300## Querying the Number of Components Associated with a State Variable
301
302During application development, you can use HiDumper to view the number of components associated with a state variable for performance optimization. For details, see [State Variable Component Location Tool Practice](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/performance/state_variable_dfx_pratice.md).
303
304
305## Avoid Frequent Reads of State Variables in a Loop
306
307Avoid frequent reads of state variables inside a loop, such as the **for** and **while** loop. A best practice is to read state variables outside a loop.
308
309[Incorrect Usage]
310
311```ts
312@Entry
313@Component
314struct Index {
315  @State message: string = '';
316
317  build() {
318    Column() {
319      Button ('Print Log')
320        .onClick(() => {
321          for (let i = 0; i < 10; i++) {
322            hilog.info(0x0000, 'TAG', '%{public}s', this.message);
323          }
324        })
325        .width('90%')
326        .backgroundColor(Color.Blue)
327        .fontColor(Color.White)
328        .margin({
329          top: 10
330        })
331    }
332    .justifyContent(FlexAlign.Start)
333    .alignItems(HorizontalAlign.Center)
334    .margin({
335      top: 15
336    })
337  }
338}
339```
340
341[Correct Usage]
342
343```ts
344@Entry
345@Component
346struct Index {
347  @State message: string = '';
348
349  build() {
350    Column() {
351      Button ('Print Log')
352        .onClick(() => {
353          let logMessage: string = this.message;
354          for (let i = 0; i < 10; i++) {
355            hilog.info(0x0000, 'TAG', '%{public}s', logMessage);
356          }
357        })
358        .width('90%')
359        .backgroundColor(Color.Blue)
360        .fontColor(Color.White)
361        .margin({
362          top: 10
363        })
364    }
365    .justifyContent(FlexAlign.Start)
366    .alignItems(HorizontalAlign.Center)
367    .margin({
368      top: 15
369    })
370  }
371}
372```
373
374## Using Temporary Variables instead of State Variables
375
376During application development, you should reduce direct value changes to the state variables and compute data by using temporary variables.
377
378When a state variable changes, ArkUI queries the components that require the use of state variables and executes an update method to render the components. However, by computing the temporary variables instead of directly changing the state variables, ArkUI can query and render components only when the last state variable changes, reducing unnecessary behaviors and improving application performance. For details about the behavior of state variables, see [@State Decorator: State Owned by Component](arkts-state.md).
379
380[Incorrect Usage]
381
382```ts
383import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';
384
385@Entry
386@Component
387struct Index {
388  @State message: string = '';
389
390  appendMsg(newMsg: string) {
391    // Performance Tracing
392    hiTraceMeter.startTrace('StateVariable', 1);
393    this.message += newMsg;
394    this.message += ';';
395    this.message += '<br/>';
396    hiTraceMeter.finishTrace('StateVariable', 1);
397  }
398
399  build() {
400    Column() {
401      Button ('Print Log')
402        .onClick(() => {
403          this.appendMsg('Change State Variables');
404        })
405        .width('90%')
406        .backgroundColor(Color.Blue)
407        .fontColor(Color.White)
408        .margin({
409          top: 10
410        })
411    }
412    .justifyContent(FlexAlign.Start)
413    .alignItems(HorizontalAlign.Center)
414    .margin({
415      top: 15
416    })
417  }
418}
419```
420
421In this case, state variables are directly changed, triggering the computation for three times. The running duration is as follows.
422
423![](figures/hp_arkui_use_state_var.png)
424
425[Correct Usage]
426
427```ts
428import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';
429
430@Entry
431@Component
432struct Index {
433  @State message: string = '';
434
435  appendMsg(newMsg: string) {
436    // Performance Tracing
437    hiTraceMeter.startTrace('TemporaryVariable', 2);
438    let message = this.message;
439    message += newMsg;
440    message += ';';
441    message += '<br/>';
442    this.message = message;
443    hiTraceMeter.finishTrace('TemporaryVariable', 2);
444  }
445
446  build() {
447    Column() {
448      Button ('Print Log')
449        .onClick(() => {
450          this.appendMsg('Change Temporary Variables');
451        })
452        .width('90%')
453        .backgroundColor(Color.Blue)
454        .fontColor(Color.White)
455        .margin({
456          top: 10
457        })
458    }
459    .justifyContent(FlexAlign.Start)
460    .alignItems(HorizontalAlign.Center)
461    .margin({
462      top: 15
463    })
464  }
465}
466```
467
468In this case, temporary variables are used instead of state variables, triggering the computation for three times. The running duration is as follows.
469
470![](figures/hp_arkui_use_local_var.png)
471
472[Summary]
473| **Computation Method**| **Time Required (for Reference Only)** | **Description**|
474| ------ | ------- | ------------------------------------- |
475| Changing state variables | 1.01ms | Increases unnecessary query and rendering of ArkUI, causing poor performance.|
476| Using temporary variables for computing | 0.63ms | Streamlines ArkUI behaviors and improve application performance.|
477<!--no_check-->
478