We use essential cookies for the website to function, as well as analytics cookies for analyzing and creating statistics of the website performance. To agree to the use of analytics cookies, click "Accept All". You can manage your preferences at any time by clicking "Cookie Settings" on the footer. More Information.

Only Essential Cookies
Accept All

@Local Decorator: Representing the Internal State of Components

You can use @Local, a variable decorator in state management V2, to observe the variable changes in custom components decorated by @ComponentV2.

Before reading this document, you are advised to read @ComponentV2. For details about common problems, see In-Component State Management FAQs.

NOTE

The @Local decorator is supported since API version 12.

This decorator can be used in atomic services since API version 12.

This decorator can be used in ArkTS widgets since API version 23.

Overview

@Local establishes the internal state of custom components with change observation capabilities.

  • Variables decorated by @Local must be initialized inside the component declaration. They cannot be initialized externally..

  • When a variable decorated by @Local changes, the component that uses the variable is re-rendered.

  • @Local supports the following types: Primitive types: number, boolean, string

    Object types: object, class

    Built-in types: Array, Set, Map, Date

  • @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.

  • @Local supports null, undefined, and union types.

Limitations of the @State Decorator in State Management V1

In state management V1, the @State decorator 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.

Collapse
Word wrap
Dark theme
Copy code
  1. class ComponentInfo {
  2. public name: string;
  3. public count: number;
  4. public message: string;
  5. constructor(name: string, count: number, message: string) {
  6. this.name = name;
  7. this.count = count;
  8. this.message = message;
  9. }
  10. }
  11. @Component
  12. struct Child {
  13. @State componentInfo: ComponentInfo = new ComponentInfo('Child', 1, 'Hello World'); // componentInfo provided by the parent component will override this initial value.
  14. build() {
  15. Column() {
  16. Text(`componentInfo.message is ${this.componentInfo.message}`)
  17. }
  18. }
  19. }
  20. @Entry
  21. @Component
  22. struct Index {
  23. build() {
  24. Column() {
  25. Child({ componentInfo: new ComponentInfo('Unknown', 0, 'Error') })
  26. }
  27. }
  28. }

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

Decorator Description

Expand
@Local Variable Decorator Description
Decorator parameters None.
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.
Initial value for the decorated variable Local initialization is required. External initialization is not allowed.

Variable Passing

Expand
Behavior Description
Initialization from the parent component Variables decorated by @Local can only be initialized locally.
Child component initialization Variables decorated by @Local can initialize variables decorated by @Param in child components.

Observed Changes

Variables decorated by @Local are observable. When a decorated variable changes, the UI component bound to the variable will be re-rendered.

  • When the decorated variable is of boolean, string, or number type, value changes to the variable can be observed.

    Collapse
    Word wrap
    Dark theme
    Copy code
    1. @Entry
    2. @ComponentV2
    3. struct Index {
    4. // Number of clicks.
    5. @Local count: number = 0;
    6. @Local message: string = 'Hello';
    7. @Local flag: boolean = false;
    8. build() {
    9. Column() {
    10. Text(`${this.count}`)
    11. Text(`${this.message}`)
    12. Text(`${this.flag}`)
    13. Button('change Local')
    14. .onClick(() => {
    15. // For variables of primitive types, @Local can observe value changes to variables.
    16. this.count++;
    17. this.message += ' World';
    18. this.flag = !this.flag;
    19. })
    20. }
    21. }
    22. }
  • 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 and @Trace decorators. Note that before API version 19, @Local cannot be used with class instance objects decorated by @Observed. 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 (API Version 19 and Later).

    Collapse
    Word wrap
    Dark theme
    Copy code
    1. class RawObject {
    2. public name: string;
    3. constructor(name: string) {
    4. this.name = name;
    5. }
    6. }
    7. @ObservedV2
    8. class ObservedObject {
    9. @Trace public name: string;
    10. constructor(name: string) {
    11. this.name = name;
    12. }
    13. }
    14. @Entry
    15. @ComponentV2
    16. struct Index {
    17. @Local rawObject: RawObject = new RawObject('rawObject');
    18. @Local observedObject: ObservedObject = new ObservedObject('observedObject');
    19. build() {
    20. Column() {
    21. Text(`${this.rawObject.name}`)
    22. Text(`${this.observedObject.name}`)
    23. Button('change object')
    24. .onClick(() => {
    25. // Changes to the overall assignment of the class object can be observed.
    26. this.rawObject = new RawObject('new rawObject');
    27. this.observedObject = new ObservedObject('new observedObject');
    28. })
    29. Button('change name')
    30. .onClick(() => {
    31. // @Local does not support observation of changes to class member property assignments. Therefore, value changes of rawObject.name cannot be observed.
    32. this.rawObject.name = 'new rawObject name';
    33. // The name property of ObservedObject is decorated by @Trace. Therefore, value changes of observedObject.name can be observed.
    34. this.observedObject.name = 'new observedObject name';
    35. })
    36. }
    37. }
    38. }
  • 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.

    Collapse
    Word wrap
    Dark theme
    Copy code
    1. @Entry
    2. @ComponentV2
    3. struct Index {
    4. @Local numArr: number[] = [1, 2, 3, 4, 5]; // @Local decorated 1D array
    5. @Local dimensionTwo: number[][] = [[1, 2, 3], [4, 5, 6]]; // @Local decorated 2D array
    6. build() {
    7. Column() {
    8. Text(`${this.numArr[0]}`)
    9. Text(`${this.numArr[1]}`)
    10. Text(`${this.numArr[2]}`)
    11. Text(`${this.dimensionTwo[0][0]}`)
    12. Text(`${this.dimensionTwo[1][1]}`)
    13. Button('change array item') // Button 1: modifies specific elements in the array.
    14. .onClick(() => {
    15. this.numArr[0]++;
    16. this.numArr[1] += 2;
    17. this.dimensionTwo[0][0] = 0;
    18. this.dimensionTwo[1][1] = 0;
    19. })
    20. Button('change whole array') // Button 2: replaces the entire array.
    21. .onClick(() => {
    22. this.numArr = [5, 4, 3, 2, 1];
    23. this.dimensionTwo = [[7, 8, 9], [0, 1, 2]];
    24. })
    25. }
    26. }
    27. }
  • 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.

    Collapse
    Word wrap
    Dark theme
    Copy code
    1. @ObservedV2
    2. class Region {
    3. @Trace public x: number;
    4. @Trace public y: number;
    5. constructor(x: number, y: number) {
    6. this.x = x;
    7. this.y = y;
    8. }
    9. }
    10. @ObservedV2
    11. class Info {
    12. @Trace public region: Region;
    13. @Trace public name: string;
    14. constructor(name: string, x: number, y: number) {
    15. this.name = name;
    16. this.region = new Region(x, y);
    17. }
    18. }
    19. @Entry
    20. @ComponentV2
    21. struct Index {
    22. @Local infoArr: Info[] = [new Info('Ocean', 28, 120), new Info('Mountain', 26, 20)];
    23. @Local originInfo: Info = new Info('Origin', 0, 0);
    24. build() {
    25. Column() {
    26. ForEach(this.infoArr, (info: Info) => {
    27. Row() {
    28. Text(`name: ${info.name}`)
    29. Text(`region: ${info.region.x}-${info.region.y}`)
    30. }
    31. })
    32. Row() {
    33. Text(`Origin name: ${this.originInfo.name}`)
    34. Text(`Origin region: ${this.originInfo.region.x}-${this.originInfo.region.y}`)
    35. }
    36. Button('change infoArr item')
    37. .onClick(() => {
    38. // Because the name property is decorated by @Trace, it can be observed.
    39. this.infoArr[0].name = 'Win';
    40. })
    41. Button('change originInfo')
    42. .onClick(() => {
    43. // Because the originInfo variable is decorated by @Local, it can be observed.
    44. this.originInfo = new Info('Origin', 100, 100);
    45. })
    46. Button('change originInfo region')
    47. .onClick(() => {
    48. // Because the x and y properties are decorated by @Trace, it can be observed.
    49. this.originInfo.region.x = 25;
    50. this.originInfo.region.y = 25;
    51. })
    52. }
    53. }
    54. }
  • 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.

    Expand
    Type APIs That Can Be Observed
    Array push, pop, shift, unshift, splice, copyWithin, fill, reverse, sort
    Date setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds
    Map set, clear, delete
    Set add, clear, delete

Constraints

The @Local decorator has the following constraints:

  • The @Local decorator can be used only in custom components decorated with the @ComponentV2 decorator.

    Collapse
    Word wrap
    Dark theme
    Copy code
    1. @ComponentV2
    2. struct MyComponent {
    3. @Local message: string = 'Hello World'; // Correct usage.
    4. build() {
    5. }
    6. }
    7. @Component
    8. struct TestComponent {
    9. @Local message: string = 'Hello World'; // Incorrect usage. An error is reported at compile time.
    10. build() {
    11. }
    12. }
  • @Local decorated variables represent internal state of components and cannot be initialized externally.

    Collapse
    Word wrap
    Dark theme
    Copy code
    1. @ComponentV2
    2. struct ChildComponent {
    3. @Local message: string = 'Hello World';
    4. build() {
    5. }
    6. }
    7. @ComponentV2
    8. struct MyComponent {
    9. build() {
    10. ChildComponent({ message: 'Hello' }) // Incorrect usage. An error is reported at compile time.
    11. }
    12. }

Comparison Between @Local and @State

The following table compares the usage and functionality of @Local and @State.

Expand
Usage @State @Local
Feature None. None.
Initialization from the parent component Optional. Not allowed.
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.
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.

Use Scenarios

Observing Overall Changes of Objects

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

Collapse
Word wrap
Dark theme
Copy code
  1. @ObservedV2
  2. class Info {
  3. @Trace public name: string;
  4. @Trace public age: number;
  5. constructor(name: string, age: number) {
  6. this.name = name;
  7. this.age = age;
  8. }
  9. }
  10. @Entry
  11. @ComponentV2
  12. struct Index {
  13. info: Info = new Info('Tom', 25);
  14. @Local localInfo: Info = new Info('Tom', 25);
  15. build() {
  16. Row() {
  17. Column() {
  18. Text(`info: ${this.info.name}-${this.info.age}`) // Text1
  19. .margin(10)
  20. Text(`localInfo: ${this.localInfo.name}-${this.localInfo.age}`) // Text2
  21. .margin(10)
  22. Button('change info&localInfo')
  23. .onClick(() => {
  24. this.info = new Info('Lucy', 18); // Text1 is not updated.
  25. this.localInfo = new Info('Lucy', 18); // Text2 is updated.
  26. })
  27. .margin(10)
  28. }
  29. .width('100%')
  30. }
  31. .height('100%')
  32. }
  33. }

Decorating Variables of the Array Type

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

Collapse
Word wrap
Dark theme
Copy code
  1. class Fruit {
  2. public name: string;
  3. constructor(name: string) {
  4. this.name = name;
  5. }
  6. }
  7. @Entry
  8. @ComponentV2
  9. struct Index {
  10. @Local fruits: Fruit[] = [new Fruit('apple'), new Fruit('banana')]; // @Local decorated variables of the Array type.
  11. build() {
  12. Row() {
  13. Column() {
  14. ForEach(this.fruits, (item: Fruit) => {
  15. Text(`${item.name}`)
  16. .fontSize(20)
  17. .margin(10)
  18. })
  19. // Reassign the value of the array, triggering UI update.
  20. Button('Reset array')
  21. .onClick(() => {
  22. this.fruits = [new Fruit('strawberry'), new Fruit('blueberry')];
  23. })
  24. .width(300)
  25. .margin(10)
  26. //Add an element to the array, triggering UI update.
  27. Button('Push element')
  28. .onClick(() => {
  29. this.fruits.push(new Fruit('cherry'));
  30. })
  31. .width(300)
  32. .margin(10)
  33. // Reverse the array elements, triggering UI update.
  34. Button('Reverse array')
  35. .onClick(() => {
  36. this.fruits.reverse();
  37. })
  38. .width(300)
  39. .margin(10)
  40. //Use the same element to fill the array, triggering UI update.
  41. Button('Fill array')
  42. .onClick(() => {
  43. this.fruits.fill(new Fruit('apple'));
  44. })
  45. .width(300)
  46. .margin(10)
  47. }
  48. .width('100%')
  49. }
  50. .height('100%')
  51. }
  52. }

Decorating Variables of the Date Type

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

Collapse
Word wrap
Dark theme
Copy code
  1. @Entry
  2. @ComponentV2
  3. struct DatePickerExample {
  4. @Local selectedDate: Date = new Date('2021-08-08'); // @Local decorated Date object
  5. build() {
  6. Row() {
  7. Column() {
  8. // Reassign a new Date object to selectedDate, triggering UI update.
  9. Button('set selectedDate to 2023-07-08')
  10. .onClick(() => {
  11. this.selectedDate = new Date('2023-07-08');
  12. })
  13. .margin(10)
  14. .width(300)
  15. // Call the setFullYear API of Date to change the year, triggering UI update.
  16. Button('increase the year by 1')
  17. .onClick(() => {
  18. this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1);
  19. })
  20. .margin(10)
  21. .width(300)
  22. // Call the setMonth API of Date to change the month, triggering UI update.
  23. Button('increase the month by 1')
  24. .onClick(() => {
  25. this.selectedDate.setMonth(this.selectedDate.getMonth() + 1);
  26. })
  27. .margin(10)
  28. .width(300)
  29. // Call the setDate API of Date to change the date, triggering UI update.
  30. Button('increase the day by 1')
  31. .onClick(() => {
  32. this.selectedDate.setDate(this.selectedDate.getDate() + 1);
  33. })
  34. .margin(10)
  35. .width(300)
  36. DatePicker({
  37. start: new Date('1970-1-1'),
  38. end: new Date('2100-1-1'),
  39. selected: this.selectedDate
  40. }).margin(20)
  41. }
  42. .width('100%')
  43. }
  44. .height('100%')
  45. }
  46. }

Decorating Variables of the Map Type

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

Collapse
Word wrap
Dark theme
Copy code
  1. @Entry
  2. @ComponentV2
  3. struct MapSample {
  4. @Local fruits: Map<string, number> = new Map([['apple', 1], ['banana', 2]]); // @Local decorated variables of the Map type.
  5. build() {
  6. Row() {
  7. Column() {
  8. ForEach(Array.from(this.fruits.entries()), (item: [string, number]) => {
  9. Text(`key: ${item[0]}, value: ${item[1]}`)
  10. .fontSize(20)
  11. .margin(10)
  12. })
  13. // Add a key-value pair, triggering UI update.
  14. Button('Set entry cherry')
  15. .onClick(() => {
  16. this.fruits.set('cherry', 3);
  17. })
  18. .width(300)
  19. .margin(10)
  20. // Update a key-value pair, triggering UI update.
  21. Button('Update entry apple')
  22. .onClick(() => {
  23. this.fruits.set('apple', 4);
  24. })
  25. .width(300)
  26. .margin(10)
  27. // Delete a key-value pair, triggering UI update.
  28. Button('Delete entry apple')
  29. .onClick(() => {
  30. this.fruits.delete('apple');
  31. })
  32. .width(300)
  33. .margin(10)
  34. // Reassign values to the entire Map, triggering UI update.
  35. Button('Reset map')
  36. .onClick(() => {
  37. this.fruits = new Map([['strawberry', 9], ['blueberry', 8]]);
  38. })
  39. .width(300)
  40. .margin(10)
  41. // Clear the values of the entire Map, triggering UI update.
  42. Button('Clear map')
  43. .onClick(() => {
  44. this.fruits.clear();
  45. })
  46. .width(300)
  47. .margin(10)
  48. }
  49. .width('100%')
  50. }
  51. .height('100%')
  52. }
  53. }

Decorating Variables of the Set Type

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

Collapse
Word wrap
Dark theme
Copy code
  1. @Entry
  2. @ComponentV2
  3. struct SetSample {
  4. @Local fruits: Set<string> = new Set(['apple', 'banana']); // @Local decorated variables of the Set type.
  5. build() {
  6. Row() {
  7. Column() {
  8. ForEach(Array.from(this.fruits.entries()), (item: [number, number]) => {
  9. Text(`${item[0]}`)
  10. .fontSize(20)
  11. .margin(10)
  12. })
  13. // New element, triggering UI update
  14. Button('Add element')
  15. .onClick(() => {
  16. this.fruits.add('cherry');
  17. })
  18. .width(300)
  19. .margin(10)
  20. // Delete an element, triggering UI update.
  21. Button('Delete element apple')
  22. .onClick(() => {
  23. this.fruits.delete('apple');
  24. })
  25. .width(300)
  26. .margin(10)
  27. // Reassign values to the entire Set, triggering UI update.
  28. Button('Reset set')
  29. .onClick(() => {
  30. this.fruits = new Set(['strawberry', 'blueberry']);
  31. })
  32. .width(300)
  33. .margin(10)
  34. // Clear the values of the entire Set, triggering UI update.
  35. Button('Clear set')
  36. .onClick(() => {
  37. this.fruits.clear();
  38. })
  39. .width(300)
  40. .margin(10)
  41. }
  42. .width('100%')
  43. }
  44. .height('100%')
  45. }
  46. }

Decorating Variables of the Union Type

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

Collapse
Word wrap
Dark theme
Copy code
  1. @Entry
  2. @ComponentV2
  3. struct Index {
  4. @Local count: number | undefined = 10; // @Local decorated variable of the union type
  5. build() {
  6. Row() {
  7. Column() {
  8. Text(`count: ${this.count}`)
  9. // Change the union variable from number to undefined, triggering the UI update.
  10. Button('change to undefined')
  11. .onClick(() => {
  12. this.count = undefined;
  13. })
  14. .width(300)
  15. .margin(10)
  16. // Change the union variable from undefined to number, triggering the UI update.
  17. Button('change to number')
  18. .onClick(() => {
  19. this.count = 10;
  20. })
  21. .width(300)
  22. .margin(10)
  23. }
  24. .width('100%')
  25. }
  26. .height('100%')
  27. }
  28. }

FAQs

Using animateTo Failed in State Management V2

In the following scenario, animateTo cannot be directly used in state management V2.

Collapse
Word wrap
Dark theme
Copy code
  1. @Entry
  2. @ComponentV2
  3. struct Index {
  4. @Local w: number = 50; // Width.
  5. @Local h: number = 50; // Height.
  6. @Local message: string = 'Hello';
  7. build() {
  8. Column() {
  9. Button('change size')
  10. .margin(20)
  11. .onClick(() => {
  12. // Values are changed additionally before the animation is executed.
  13. this.w = 100;
  14. this.h = 100;
  15. this.message = 'Hello World';
  16. this.getUIContext().animateTo({
  17. duration: 1000
  18. }, () => {
  19. this.w = 200;
  20. this.h = 200;
  21. this.message = 'Hello ArkUI';
  22. })
  23. })
  24. Column() {
  25. Text(`${this.message}`)
  26. }
  27. .backgroundColor('#ff17a98d')
  28. .width(this.w)
  29. .height(this.h)
  30. }
  31. }
  32. }

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

Since API version 22, you can use the applySync APIs to achieve the expected display effect.

Collapse
Word wrap
Dark theme
Copy code
  1. import { UIUtils } from '@kit.ArkUI';
  2. @Entry
  3. @ComponentV2
  4. struct Index {
  5. @Local w: number = 50; // Width.
  6. @Local h: number = 50; // Height.
  7. @Local message: string = 'Hello';
  8. build() {
  9. Column() {
  10. Button('change size')
  11. .margin(20)
  12. .onClick(() => {
  13. // Values are changed additionally before the animation is executed.
  14. UIUtils.applySync(() => {
  15. this.w = 100;
  16. this.h = 100;
  17. this.message = 'Hello World';
  18. })
  19. this.getUIContext().animateTo({
  20. duration: 1000
  21. }, () => {
  22. this.w = 200;
  23. this.h = 200;
  24. this.message = 'Hello ArkUI';
  25. })
  26. })
  27. Column() {
  28. Text(`${this.message}`)
  29. }
  30. .backgroundColor('#ff17a98d')
  31. .width(this.w)
  32. .height(this.h)
  33. }
  34. }
  35. }

The principle is as follows: Use the applySync API to synchronously update the status variables changes in the closure function, and then execute the original animation to achieve the expected effect.

Search in Guides
Enter a keyword.