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