1# \@Local Decorator: Representing the Internal State of Components 2 3You can use \@Local, a variable decorator in state management V2, to observe the variable changes in custom components decorated by \@ComponentV2. 4 5 6Before reading this topic, you are advised to read [\@ComponentV2](./arkts-new-componentV2.md). 7 8>**NOTE** 9> 10>The \@Local decorator is supported since API version 12. 11> 12 13## Overview 14 15\@Local indicates the internal state of a component, which enables the variables in the custom component to observe changes: 16 17- Variables decorated by \@Local cannot be initialized externally. They must be initialized inside the component. 18 19- When a variable decorated by \@Local changes, the component that uses the variable is re-rendered. 20 21- \@Local can observe basic types such as number, boolean, string, object, and class and built-in types such as Array, Set, Map, and Date. 22 23- \@Local can observe only the variable it decorates. If the decorated variable is of the simple type, it can observe value changes to the variable; if the decorated variable is of the object type, it can observe value changes to the entire object; if the decorated variable is of the array type, it can observe changes of the entire array and its items; if the decorated variable is of the built-in types, such as Array, Set, Map, and Date, it can observe changes caused by calling the APIs. For details, see [Observed Changes](#observed-changes). 24 25- \@Local supports null, undefined, and union types. 26 27## Limitations of the \@State decorator in State Management V1 28 29State management V1 uses the [\@State](arkts-state.md) decorator to define state variables in a class. However, because the \@State decorator allows variables to be initialized externally, it cannot accurately express the semantics that the internal state of the component cannot be modified externally. 30 31```ts 32class ComponentInfo { 33 name: string; 34 count: number; 35 message: string; 36 constructor(name: string, count: number, message: string) { 37 this.name = name; 38 this.count = count; 39 this.message = message; 40 } 41} 42@Component 43struct Child { 44 @State componentInfo: ComponentInfo = new ComponentInfo("Child", 1, "Hello World"); 45 46 build() { 47 Column() { 48 Text(`componentInfo.message is ${this.componentInfo.message}`) 49 } 50 } 51} 52@Entry 53@Component 54struct Index { 55 build() { 56 Column() { 57 Child({componentInfo: new ComponentInfo("Unknown", 0, "Error")}) 58 } 59 } 60} 61``` 62 63In the preceding code, the initialization of the **Child** component can pass in a new value to overwrite the local value of **componentInfo** that the component wants to use as an internal state variable. However, the **Child** component cannot detect that **componentInfo** has been initialized externally, which is inconvenient for managing the internal state of the component. This is where \@Local, a decorator that represents the internal state of components, comes into the picture. 64 65## Decorator Description 66 67| \@Local Variable Decorator| Description| 68| ------------------- | ------------------------------------------------------------ | 69| Decorator parameters| None.| 70| Allowed variable types| Basic types, such as object, class, string, number, boolean, and enum, and built-in types such as Array, Date, Map, and Set. null, undefined, and union types.| 71| Initial value for the decorated variable| Local initialization is required. External initialization is not allowed.| 72 73## Variable Passing 74 75| Passing Rules | Description | 76| -------------- | --------------------------------------------------------- | 77| Initialization from the parent component| Variables decorated by \@Local can only be initialized locally.| 78| Child component initialization | Variables decorated by \@Local can initialize variables decorated by \@Param in the child components. | 79 80## Observed Changes 81 82Variables decorated by \@Local are observable. When a decorated variable changes, the UI component bound to the variable will be re-rendered. 83 84- When the decorated variable is of boolean, string, or number type, value changes to the variable can be observed. 85 86 ```ts 87 @Entry 88 @ComponentV2 89 struct Index { 90 @Local count: number = 0; 91 @Local message: string = "Hello"; 92 @Local flag: boolean = false; 93 build() { 94 Column() { 95 Text(`${this.count}`) 96 Text(`${this.message}`) 97 Text(`${this.flag}`) 98 Button("change Local") 99 .onClick(()=>{ 100 // When @Local decorates a simple type, it can observe value changes to the variable. 101 this.count++; 102 this.message += " World"; 103 this.flag = !this.flag; 104 }) 105 } 106 } 107 } 108 ``` 109 110- When the decorated variable is of a class object type, only the overall value changes to the class object can be observed. To observe value changes to the member properties in the class object, you'll need the \@ObservedV2 and \@Trace decorators. Note that \@Local cannot be used together with the instance objects of the \@Observed decorated class. 111 112 ```ts 113 class RawObject { 114 name: string; 115 constructor(name: string) { 116 this.name = name; 117 } 118 } 119 @ObservedV2 120 class ObservedObject { 121 @Trace name: string; 122 constructor(name: string) { 123 this.name = name; 124 } 125 } 126 @Entry 127 @ComponentV2 128 struct Index { 129 @Local rawObject: RawObject = new RawObject("rawObject"); 130 @Local observedObject: ObservedObject = new ObservedObject("observedObject"); 131 build() { 132 Column() { 133 Text(`${this.rawObject.name}`) 134 Text(`${this.observedObject.name}`) 135 Button("change object") 136 .onClick(() => { 137 // Value changes to the class object can be observed. 138 this.rawObject = new RawObject("new rawObject"); 139 this.observedObject = new ObservedObject("new observedObject"); 140 }) 141 Button("change name") 142 .onClick(() => { 143 // @Local does not have the capability of observing class object property. Therefore, value changes of rawObject.name cannot be observed. 144 this.rawObject.name = "new rawObject name"; 145 // The name property of ObservedObject is decorated by @Trace. Therefore, value changes of observedObject.name can be observed. 146 this.observedObject.name = "new observedObject name"; 147 }) 148 } 149 } 150 } 151 ``` 152 153- When the decorated variable is of a simple array type, changes of the entire array or its items can be observed. 154 155 ```ts 156 @Entry 157 @ComponentV2 158 struct Index { 159 @Local numArr: number[] = [1,2,3,4,5]; 160 @Local dimensionTwo: number[][] = [[1,2,3],[4,5,6]]; 161 162 build() { 163 Column() { 164 Text(`${this.numArr[0]}`) 165 Text(`${this.numArr[1]}`) 166 Text(`${this.numArr[2]}`) 167 Text(`${this.dimensionTwo[0][0]}`) 168 Text(`${this.dimensionTwo[1][1]}`) 169 Button("change array item") 170 .onClick(() => { 171 this.numArr[0]++; 172 this.numArr[1] += 2; 173 this.dimensionTwo[0][0] = 0; 174 this.dimensionTwo[1][1] = 0; 175 }) 176 Button("change whole array") 177 .onClick(() => { 178 this.numArr = [5,4,3,2,1]; 179 this.dimensionTwo = [[7,8,9],[0,1,2]]; 180 }) 181 } 182 } 183 } 184 ``` 185 186- When the decorated variable is of a nested type or an object array, changes of lower-level object properties cannot be observed. Observation of these lower-level object properties requires use of \@ObservedV2 and \@Trace decorators. 187 188 ```ts 189 @ObservedV2 190 class Region { 191 @Trace x: number; 192 @Trace y: number; 193 constructor(x: number, y: number) { 194 this.x = x; 195 this.y = y; 196 } 197 } 198 @ObservedV2 199 class Info { 200 @Trace region: Region; 201 @Trace name: string; 202 constructor(name: string, x: number, y: number) { 203 this.name = name; 204 this.region = new Region(x, y); 205 } 206 } 207 @Entry 208 @ComponentV2 209 struct Index { 210 @Local infoArr: Info[] = [new Info("Ocean", 28, 120), new Info("Mountain", 26, 20)]; 211 @Local originInfo: Info = new Info("Origin", 0, 0); 212 build() { 213 Column() { 214 ForEach(this.infoArr, (info: Info) => { 215 Row() { 216 Text(`name: ${info.name}`) 217 Text(`region: ${info.region.x}-${info.region.y}`) 218 } 219 }) 220 Row() { 221 Text(`Origin name: ${this.originInfo.name}`) 222 Text(`Origin region: ${this.originInfo.region.x}-${this.originInfo.region.y}`) 223 } 224 Button("change infoArr item") 225 .onClick(() => { 226 // Because the name property is decorated by @Trace, it can be observed. 227 this.infoArr[0].name = "Win"; 228 }) 229 Button("change originInfo") 230 .onClick(() => { 231 // Because the originInfo variable is decorated by @Local, it can be observed. 232 this.originInfo = new Info("Origin", 100, 100); 233 }) 234 Button("change originInfo region") 235 .onClick(() => { 236 // Because the x and y properties are decorated by @Trace, it can be observed. 237 this.originInfo.region.x = 25; 238 this.originInfo.region.y = 25; 239 }) 240 } 241 } 242 } 243 ``` 244 245- When the decorated variable is of a built-in type, you can observe the overall value changes of the variable and the changes caused by calling the APIs listed below. 246 247 | Type | Observable APIs | 248 | ----- | ------------------------------------------------------------ | 249 | Array | push, pop, shift, unshift, splice, copyWithin, fill, reverse, sort| 250 | Date | setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds | 251 | Map | set, clear, delete | 252 | Set | add, clear, delete | 253 254## Constraints 255 256The \@Local decorator has the following constraints: 257 258- The \@Local decorator can be used only in custom components decorated by \@ComponentV2. 259 260 ```ts 261 @ComponentV2 262 struct MyComponent { 263 @Local message: string = "Hello World"; // Correct usage. 264 build() { 265 } 266 } 267 @Component 268 struct TestComponent { 269 @Local message: string = "Hello World"; // Incorrect usage. An error is reported during compilation. 270 build() { 271 } 272 } 273 ``` 274 275- The variable decorated by \@Local indicates the internal state of the component and cannot be initialized externally. 276 277 ```ts 278 @ComponentV2 279 struct ChildComponent { 280 @Local message: string = "Hello World"; 281 build() { 282 } 283 } 284 @ComponentV2 285 struct MyComponent { 286 build() { 287 ChildComponent({ message: "Hello" }) // Incorrect usage. An error is reported during compilation. 288 } 289 } 290 ``` 291 292## Comparison Between \@Local and \@State 293 294The following table compares the usage and functions of \@Local and \@State. 295 296| | \@State | \@Local | 297| ------------------ | ---------------------------- | --------------------------------- | 298| Parameter | None. | None. | 299| Initialization from the parent component | Optional. | External initialization is not allowed. | 300| Observation capability| Variables and top-level member properties can be observed, but lower-level member properties cannot.| The variable itself can be observed. Lower-level observation requires use of \@Trace decorator.| 301| Data Transfer| It can be used as a data source to synchronize with the state variables in a child component.| It can be used as a data source to synchronize with the state variables in a child component.| 302 303## Use Scenarios 304 305### Observing Overall Changes of Objects 306 307When a class object and its properties are decorated by \@ObservedV2 and \@Trace, properties in the class object can be observed. However, value changes of the class object itself cannot be observed and do not initiate UI re-renders. In this case, you can use \@Local to decorate the object to observe the changes. 308 309```ts 310@ObservedV2 311class Info { 312 @Trace name: string; 313 @Trace age: number; 314 constructor(name: string, age: number) { 315 this.name = name; 316 this.age = age; 317 } 318} 319@Entry 320@ComponentV2 321struct Index { 322 info: Info = new Info("Tom", 25); 323 @Local localInfo: Info = new Info("Tom", 25); 324 build() { 325 Column() { 326 Text(`info: ${this.info.name}-${this.info.age}`) // Text1 327 Text(`localInfo: ${this.localInfo.name}-${this.localInfo.age}`) // Text2 328 Button("change info&localInfo") 329 .onClick(() => { 330 this.info = new Info("Lucy", 18); // Text1 is not updated. 331 this.localInfo = new Info("Lucy", 18); // Text2 is updated. 332 }) 333 } 334 } 335} 336``` 337 338### Decorating Variables of the Date Type 339 340When the decorated object is of the **Date** type, the overall value changes of **Date** can be observed. In addition, you can call the following APIs to update **Date** properties: **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, and **setUTCMilliseconds**. 341 342```ts 343@Entry 344@ComponentV2 345struct DatePickerExample { 346 @Local selectedDate: Date = new Date('2021-08-08'); 347 348 build() { 349 Column() { 350 Button('set selectedDate to 2023-07-08') 351 .margin(10) 352 .onClick(() => { 353 this.selectedDate = new Date('2023-07-08'); 354 }) 355 Button('increase the year by 1') 356 .margin(10) 357 .onClick(() => { 358 this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1); 359 }) 360 Button('increase the month by 1') 361 .margin(10) 362 .onClick(() => { 363 this.selectedDate.setMonth(this.selectedDate.getMonth() + 1); 364 }) 365 Button('increase the day by 1') 366 .margin(10) 367 .onClick(() => { 368 this.selectedDate.setDate(this.selectedDate.getDate() + 1); 369 }) 370 DatePicker({ 371 start: new Date('1970-1-1'), 372 end: new Date('2100-1-1'), 373 selected: this.selectedDate 374 }) 375 }.width('100%') 376 } 377} 378``` 379 380### Decorating Variables of the Map Type 381 382When the decorated object is of the **Map** type, the overall value changes of **Map** can be observed. In addition, you can call the set, clear, and delete interfaces to update the data in **Map**. 383 384```ts 385@Entry 386@ComponentV2 387struct MapSample { 388 @Local message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]); 389 390 build() { 391 Row() { 392 Column() { 393 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 394 Text(`${item[0]}`).fontSize(30) 395 Text(`${item[1]}`).fontSize(30) 396 Divider() 397 }) 398 Button('init map').onClick(() => { 399 this.message = new Map([[0, "a"], [1, "b"], [3, "c"]]); 400 }) 401 Button('set new one').onClick(() => { 402 this.message.set(4, "d"); 403 }) 404 Button('clear').onClick(() => { 405 this.message.clear(); 406 }) 407 Button('replace the first one').onClick(() => { 408 this.message.set(0, "aa"); 409 }) 410 Button('delete the first one').onClick(() => { 411 this.message.delete(0); 412 }) 413 } 414 .width('100%') 415 } 416 .height('100%') 417 } 418} 419``` 420 421### Decorating Variables of the Set Type 422 423When the decorated object is **Set**, the overall value changes of **Set** can be observed. In addition, you can call the add, clear, and delete interfaces to update the data in **Set**. 424 425```ts 426@Entry 427@ComponentV2 428struct SetSample { 429 @Local message: Set<number> = new Set([0, 1, 2, 3, 4]); 430 431 build() { 432 Row() { 433 Column() { 434 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 435 Text(`${item[0]}`).fontSize(30) 436 Divider() 437 }) 438 Button('init set').onClick(() => { 439 this.message = new Set([0, 1, 2, 3, 4]); 440 }) 441 Button('set new one').onClick(() => { 442 this.message.add(5); 443 }) 444 Button('clear').onClick(() => { 445 this.message.clear(); 446 }) 447 Button('delete the first one').onClick(() => { 448 this.message.delete(0); 449 }) 450 } 451 .width('100%') 452 } 453 .height('100%') 454 } 455} 456``` 457 458### Union Type 459 460\@Local supports null, undefined, and union types. In the following example, the **count** type is **number | undefined**. If you click to change the **count** type, the UI will be re-rendered accordingly. 461 462```ts 463@Entry 464@ComponentV2 465struct Index { 466 @Local count: number | undefined = 10; 467 468 build() { 469 Column() { 470 Text(`count(${this.count})`) 471 Button("change to undefined") 472 .onClick(() => { 473 this.count = undefined; 474 }) 475 Button("change to number") 476 .onClick(() => { 477 this.count = 10; 478 }) 479 } 480 } 481} 482``` 483 484## FAQs 485 486### Repeated Value Changes to State Variables by Complex Constants Trigger Re-rendering 487 488```ts 489@Entry 490@ComponentV2 491struct Index { 492 list: string[][] = [['a'], ['b'], ['c']]; 493 @Local dataObjFromList: string[] = this.list[0]; 494 495 @Monitor("dataObjFromList") 496 onStrChange(monitor: IMonitor) { 497 console.log("dataObjFromList has changed"); 498 } 499 500 build() { 501 Column() { 502 Button('change to self').onClick(() => { 503 // The new value is the same as the locally initialized value. 504 this.dataObjFromList = this.list[0]; 505 }) 506 } 507 } 508} 509``` 510 511In the preceding example, each time you click Button('change to self'), the same constant of the **Array** type is assigned to a state variable of the same type, triggering re-rendering. This is because in state management V2, a proxy is added to Date, Map, Set, and Array that use state variable decorators such as @Trace and @Local to observe changes invoked by APIs. 512**dataObjFromList** is of a **Proxy** type but **list[0]** is of an **Array** type. As a result, when **list[0]** is assigned to **dataObjFromList**, the value changes trigger re-rendering. 513To avoid unnecessary value changes and re-renders, use [UIUtils.getTarget()](./arkts-new-getTarget.md) to obtain the original value and determine whether the original and new values are the same. If they are the same, do not perform value changes. 514 515Example of Using **UIUtils.getTarget()** 516 517```ts 518import { UIUtils } from '@ohos.arkui.StateManagement'; 519 520@Entry 521@ComponentV2 522struct Index { 523 list: string[][] = [['a'], ['b'], ['c']]; 524 @Local dataObjFromList: string[] = this.list[0]; 525 526 @Monitor("dataObjFromList") 527 onStrChange(monitor: IMonitor) { 528 console.log("dataObjFromList has changed"); 529 } 530 531 build() { 532 Column() { 533 Button('change to self').onClick(() => { 534 // Obtain the original value and compare it with the new value. 535 if (UIUtils.getTarget(this.dataObjFromList) !== this.list[0]) { 536 this.dataObjFromList = this.list[0]; 537 } 538 }) 539 } 540 } 541} 542``` 543 544### Using animationTo Failed in State Management V2 545 546In the following scenario, [animateTo](../reference/apis-arkui/arkui-ts/ts-explicit-animation.md) cannot be directly used in state management V2. 547 548```ts 549@Entry 550@ComponentV2 551struct Index { 552 @Local w: number = 50; // Width. 553 @Local h: number = 50; // Height. 554 @Local message: string = 'Hello'; 555 556 build() { 557 Column() { 558 Button('change size') 559 .margin(20) 560 .onClick(() => { 561 // Values are changed additionally before the animation is executed. 562 this.w = 100; 563 this.h = 100; 564 this.message = 'Hello World'; 565 animateTo({ 566 duration: 1000 567 }, () => { 568 this.w = 200; 569 this.h = 200; 570 this.message = 'Hello ArkUI'; 571 }) 572 }) 573 Column() { 574 Text(`${this.message}`) 575 } 576 .backgroundColor('#ff17a98d') 577 .width(this.w) 578 .height(this.h) 579 } 580 } 581} 582``` 583 584In the preceding code, the length and width of the green rectangle are expected to change from **100** to **200** and the string is expected to change from **Hello World** to **Hello ArkUI**. However, **animateTo** is incompatible with V2 in the refresh mechanism. Therefore, the extra change does not take effect. As a result, the length and width of the green rectangle actually change from **50** to **200** and the string changes from **Hello** to **Hello ArkUI**. 585 586 587 588Follow the method below to achieve the expected display effect temporarily. 589 590```ts 591@Entry 592@ComponentV2 593struct Index { 594 @Local w: number = 50; // Width. 595 @Local h: number = 50; // Height. 596 @Local message: string = 'Hello'; 597 598 build() { 599 Column() { 600 Button('change size') 601 .margin(20) 602 .onClick(() => { 603 // Values are changed additionally before the animation is executed. 604 this.w = 100; 605 this.h = 100; 606 this.message = 'Hello Word'; 607 animateToImmediately({ 608 duration: 0 609 }, () => { 610 }) 611 animateTo({ 612 duration: 1000 613 }, () => { 614 this.w = 200; 615 this.h = 200; 616 this.message = 'Hello ArkUI'; 617 }) 618 }) 619 Column() { 620 Text(`${this.message}`) 621 } 622 .backgroundColor('#ff17a98d') 623 .width(this.w) 624 .height(this.h) 625 } 626 } 627} 628``` 629 630Use [animateToImmediately](../reference/apis-arkui/arkui-ts/ts-explicit-animatetoimmediately.md) whose **duration** is **0** to refresh the extra change and then execute the original animation to achieve the expected effect. 631 632 633 634You are advised to use the **animateTo** API with caution in state management V2. 635