• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![arkts-new-local-animateTo-1](figures/arkts-new-local-animateTo-1.gif)
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![arkts-new-local-animateTo-2](figures/arkts-new-local-animateTo-2.gif)
677