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 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 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