• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![arkts-new-local-animateTo-1](figures/arkts-new-local-animateTo-1.gif)
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![arkts-new-local-animateTo-2](figures/arkts-new-local-animateTo-2.gif)
633
634You are advised to use the **animateTo** API with caution in state management V2.
635