• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Best Practices for State Management
2
3
4This guide outlines best practices for state management in ArkUI applications. Read on to discover the common pitfalls in state management and how to avoid them, with carefully selected examples of recommended and not-recommended practices.
5
6## 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 MyClass {
15  public num: number = 0;
16
17  constructor(num: number) {
18    this.num = num;
19  }
20}
21
22@Component
23struct PropChild {
24  @Prop testClass: MyClass; // @Prop makes a deep copy.
25
26  build() {
27    Text(`PropChild testNum ${this.testClass.num}`)
28  }
29}
30
31@Entry
32@Component
33struct Parent {
34  @State testClass: MyClass[] = [new MyClass(1)];
35
36  build() {
37    Column() {
38      Text(`Parent testNum ${this.testClass[0].num}`)
39        .onClick(() => {
40          this.testClass[0].num += 1;
41        })
42
43      // PropChild does not change the value of @Prop testClass: MyClass. Therefore, @ObjectLink is a better choice.
44      PropChild({ testClass: this.testClass[0] })
45    }
46  }
47}
48```
49
50In the preceding example, the **PropChild** component does not change the value of **\@Prop testClass: MyClass**. 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 MyClass {
57  public num: number = 0;
58
59  constructor(num: number) {
60    this.num = num;
61  }
62}
63
64@Component
65struct PropChild {
66  @ObjectLink testClass: MyClass; // @ObjectLink does not make a deep copy.
67
68  build() {
69    Text(`PropChild testNum ${this.testClass.num}`)
70  }
71}
72
73@Entry
74@Component
75struct Parent {
76  @State testClass: MyClass[] = [new MyClass(1)];
77
78  build() {
79    Column() {
80      Text(`Parent testNum ${this.testClass[0].num}`)
81        .onClick(() => {
82          this.testClass[0].num += 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({ testClass: this.testClass[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 MyComponent {
102  @State needsUpdate: boolean = true;
103  realStateArr: Array<number> = [4, 1, 3, 2]; // No state variable decorator is used.
104  realState: Color = Color.Yellow;
105
106  updateUIArr(param: Array<number>): Array<number> {
107    const triggerAGet = this.needsUpdate;
108    return param;
109  }
110  updateUI(param: Color): Color {
111    const triggerAGet = this.needsUpdate;
112    return param;
113  }
114  build() {
115    Column({ space: 20 }) {
116      ForEach(this.updateUIArr(this.realStateArr),
117        (item: Array<number>) => {
118          Text(`${item}`)
119        })
120      Text("add item")
121        .onClick(() => {
122          // Changing realStateArr does not trigger UI re-render.
123          this.realStateArr.push(this.realStateArr[this.realStateArr.length-1] + 1);
124
125          // Trigger the UI re-render.
126          this.needsUpdate = !this.needsUpdate;
127        })
128      Text("chg color")
129        .onClick(() => {
130          // Changing realState does not trigger UI re-render.
131          this.realState = this.realState == Color.Yellow ? Color.Red : Color.Yellow;
132
133          // Trigger the UI re-render.
134          this.needsUpdate = !this.needsUpdate;
135        })
136    }.backgroundColor(this.updateUI(this.realState))
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 re-render logic, but in ArkUI, this 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.realStateArr** and **this.realState** 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 **realStateArr** and **realState** variables with \@State. Then, the variable **needsUpdate** is no longer required.
153
154
155```ts
156@Entry
157@Component
158struct CompA {
159  @State realStateArr: Array<number> = [4, 1, 3, 2];
160  @State realState: Color = Color.Yellow;
161  build() {
162    Column({ space: 20 }) {
163      ForEach(this.realStateArr,
164        (item: Array<number>) => {
165          Text(`${item}`)
166        })
167      Text("add item")
168        .onClick(() => {
169          // Changing realStateArr triggers UI re-render.
170          this.realStateArr.push(this.realStateArr[this.realStateArr.length-1] + 1);
171        })
172      Text("chg color")
173        .onClick(() => {
174          // Changing realState triggers UI re-render.
175          this.realState = this.realState == Color.Yellow ? Color.Red : Color.Yellow;
176        })
177    }.backgroundColor(this.realState)
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      // 'app.media.icon' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
200      Image($r('app.media.icon'))
201        .width(50)
202        .height(50)
203        .translate({
204          x:this.translateObj.translateX // this.translateObj.translateX is bound to the Image and Text components.
205        })
206      Text("Title")
207        .fontSize(20)
208        .translate({
209          x: this.translateObj.translateX
210        })
211    }
212  }
213}
214@Entry
215@Component
216struct Page {
217  @State translateObj: Translate = new Translate();
218  build() {
219    Column() {
220      Title({
221        translateObj: this.translateObj
222      })
223      Stack() {
224      }
225      .backgroundColor("black")
226      .width(200)
227      .height(400)
228      .translate({
229        x:this.translateObj.translateX // this.translateObj.translateX is bound to the Stack and Button components.
230      })
231      Button("move")
232        .translate({
233          x:this.translateObj.translateX
234        })
235        .onClick(() => {
236          animateTo({
237            duration: 50
238          },()=>{
239            this.translateObj.translateX = (this.translateObj.translateX + 50) % 150
240          })
241        })
242    }
243  }
244}
245```
246
247In 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.
248
249[Correct Usage]
250
251```ts
252@Observed
253class Translate {
254  translateX: number = 20;
255}
256@Component
257struct Title {
258  build() {
259    Row() {
260      // 'app.media.icon' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
261      Image($r('app.media.icon'))
262        .width(50)
263        .height(50)
264      Text("Title")
265        .fontSize(20)
266    }
267  }
268}
269@Entry
270@Component
271struct Page1 {
272  @State translateObj: Translate = new Translate();
273  build() {
274    Column() {
275      Title()
276      Stack() {
277      }
278      .backgroundColor("black")
279      .width(200)
280      .height(400)
281      Button("move")
282        .onClick(() => {
283          animateTo({
284            duration: 50
285          },()=>{
286            this.translateObj.translateX = (this.translateObj.translateX + 50) % 150
287          })
288        })
289    }
290    .translate({ // The same translate attribute is set for both the Stack and Button child components on the Column layer.
291      x: this.translateObj.translateX
292    })
293  }
294}
295```
296
297## Properly Controlling the Number of Components Associated with Object State Variables
298
299
300When 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).
301
302## Querying the Number of Components Associated with a State Variable
303
304During 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).
305
306
307## Avoid Frequent Reads of State Variables in a Loop
308
309Avoid 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.
310
311[Incorrect Usage]
312
313```ts
314import hilog from '@ohos.hilog';
315
316@Entry
317@Component
318struct Index {
319  @State message: string = '';
320
321  build() {
322    Column() {
323      Button ('Print Log')
324        .onClick(() => {
325          for (let i = 0; i < 10; i++) {
326            hilog.info(0x0000, 'TAG', '%{public}s', this.message);
327          }
328        })
329        .width('90%')
330        .backgroundColor(Color.Blue)
331        .fontColor(Color.White)
332        .margin({
333          top: 10
334        })
335    }
336    .justifyContent(FlexAlign.Start)
337    .alignItems(HorizontalAlign.Center)
338    .margin({
339      top: 15
340    })
341  }
342}
343```
344
345[Correct Usage]
346
347```ts
348import hilog from '@ohos.hilog';
349
350@Entry
351@Component
352struct Index {
353  @State message: string = '';
354
355  build() {
356    Column() {
357      Button ('Print Log')
358        .onClick(() => {
359          let logMessage: string = this.message;
360          for (let i = 0; i < 10; i++) {
361            hilog.info(0x0000, 'TAG', '%{public}s', logMessage);
362          }
363        })
364        .width('90%')
365        .backgroundColor(Color.Blue)
366        .fontColor(Color.White)
367        .margin({
368          top: 10
369        })
370    }
371    .justifyContent(FlexAlign.Start)
372    .alignItems(HorizontalAlign.Center)
373    .margin({
374      top: 15
375    })
376  }
377}
378```
379
380## Using Temporary Variables instead of State Variables
381
382During application development, you should reduce direct value changes to the state variables and compute data by using temporary variables.
383
384When 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).
385
386[Incorrect Usage]
387
388```ts
389import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';
390
391@Entry
392@Component
393struct Index {
394  @State message: string = '';
395
396  appendMsg(newMsg: string) {
397    // Performance Tracing
398    hiTraceMeter.startTrace('StateVariable', 1);
399    this.message += newMsg;
400    this.message += ';';
401    this.message += '<br/>';
402    hiTraceMeter.finishTrace('StateVariable', 1);
403  }
404
405  build() {
406    Column() {
407      Button('Print Log')
408        .onClick(() => {
409          this.appendMsg('Change State Variables');
410        })
411        .width('90%')
412        .backgroundColor(Color.Blue)
413        .fontColor(Color.White)
414        .margin({
415          top: 10
416        })
417    }
418    .justifyContent(FlexAlign.Start)
419    .alignItems(HorizontalAlign.Center)
420    .margin({
421      top: 15
422    })
423  }
424}
425```
426
427In this case, state variables are directly changed, triggering the computation for three times. The running duration is as follows.
428
429![](figures/hp_arkui_use_state_var.png)
430
431[Correct Usage]
432
433```ts
434import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';
435
436@Entry
437@Component
438struct Index {
439  @State message: string = '';
440
441  appendMsg(newMsg: string) {
442    // Performance Tracing
443    hiTraceMeter.startTrace('TemporaryVariable', 2);
444    let message = this.message;
445    message += newMsg;
446    message += ';';
447    message += '<br/>';
448    this.message = message;
449    hiTraceMeter.finishTrace('TemporaryVariable', 2);
450  }
451
452  build() {
453    Column() {
454      Button('Print Log')
455        .onClick(() => {
456          this.appendMsg('Change Temporary Variables');
457        })
458        .width('90%')
459        .backgroundColor(Color.Blue)
460        .fontColor(Color.White)
461        .margin({
462          top: 10
463        })
464    }
465    .justifyContent(FlexAlign.Start)
466    .alignItems(HorizontalAlign.Center)
467    .margin({
468      top: 15
469    })
470  }
471}
472```
473
474In this case, temporary variables are used instead of state variables, triggering the computation for three times. The running duration is as follows.
475
476![](figures/hp_arkui_use_local_var.png)
477
478[Summary]
479| **Computation Method**| **Time Required (for Reference Only)** | **Description**|
480| ------ | ------- | ------------------------------------- |
481| Changing state variables | 1.01 ms| Increases unnecessary query and rendering of ArkUI, causing poor performance.|
482| Using temporary variables for computing | 0.63 ms| Streamlines ArkUI behaviors and improve application performance.|
483