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