HarmonyOS State Management with ArkTS: Component and Application-Level Patterns

@State Decorator

The @State decorator is used to declare a state variable in a component. When the state variable changes, the component automatically re-renders.

Assigning Values to Class Properties

In HarmonyOS with ArkTS, you can assign values to class properties within components. Here's an example:


@Component
struct MyComponent {
  @State count: number = 0;
  
  build() {
    Row() {
      Text(`Count: ${this.count}`)
        .fontSize(50)
        .onClick(() => {
          // Update the array based on condition
          this.arr = this.arr[0] == 1 ? [3,4,5] : [1,2,3];
        })
    }
  }
}

@Prop Decorator

The @Prop decorator creates a one-way sync relationship between parent and child components. It supports simple types like string, number, boolean, enum, and their arrays.

Class Object Properties with @Prop

When working with class objects, @Prop can sync properties from parent to child components:


class Book {  
  public title: string;  
  public pages: number;  
  public readIt: boolean = false;

  constructor(title: string, pages: number) {  
    this.title = title;  
    this.pages = pages;  
  }  
}

@Component  
struct ReaderComp {  
  @Prop title: string;  
  @Prop readIt: boolean;

  build() {  
    Row() {  
      Text(this.title)  
      Text(`... ${this.readIt ? 'I have read' : 'I have not read it'}`)  
      .onClick(() => this.readIt = true)  
    }  
  }  
}  

@Entry  
@Component  
struct Library {  
  @State book: Book = new Book('100 secrets of C++', 765);  
  build() {  
    Column() {  
      ReaderComp({ title: this.book.title, readIt: this.book.readIt })  
      ReaderComp({ title: this.book.title, readIt: this.book.readIt })  
    }  
  }  
}

@Link Decorator

The @Link decorator creates a two-way sync relationship between parent and child components. It supports simple types and arrays, but not complex types.

Simple Types and Class Objects with @Link

Here's an example of using @Link with both simple types and class objects:


class ButtonState {  
  width: number = 0;  
  constructor(width: number) {  
    this.width = width;  
  }  
}  

@Component  
struct ColorButton {  
  @Link buttonState: ButtonState;  
  @Link buttonSize: number;  
  color: string;

  build() {  
    Button(this.color + ' Button')  
      .width(this.buttonState.width)  
      .height(150.0)  
      .backgroundColor(this.color)  
      .onClick(() => {  
        if (this.buttonState.width < 700) {  
          // Update class property, changes sync back to parent  
          this.buttonState.width += 125;  
        } else {  
          // Update class instance, changes sync back to parent  
          this.buttonState = new ButtonState(100);  
        }  
      })  
  }  
}  

@Entry  
@Component  
struct ButtonContainer {  
  @State greenButtonState: ButtonState = new ButtonState(300);  
  @State yellowButtonSize: number = 100;  
  build() {  
    Column() {  
      // Simple type sync from parent @State to child @Link  
      Button('Parent: Set Yellow Button Size')  
        .onClick(() => {  
          this.yellowButtonSize = (this.yellowButtonSize < 700) ? this.yellowButtonSize + 100 : 100;  
        })  
      // Class type sync from parent @State to child @Link  
      Button('Parent: Set Green Button')  
        .onClick(() => {  
          this.greenButtonState.width = (this.greenButtonState.width < 700) ? this.greenButtonState.width + 100 : 100;  
        })  
      // Class type initialization with @Link  
      ColorButton({ buttonState: $greenButtonState, buttonSize: $yellowButtonSize, color: '#00ff00' })  
      // Simple type initialization with @Link  
      ColorButton({ buttonState: $greenButtonState, buttonSize: $yellowButtonSize, color: '#ffff00' })  
    }  
  }  
}

@Provide/@Consume Decorators

The @Provide and @Consume decorators enable two-way data synchronization between ancestor and descendant components, eliminating the need for parameter passing through multiple component levels.

Using @Provide/@Consume for Cross-Component Communication


@Component  
struct DescendantComp {  
  // @Consume binds to ancestor's @Provide with the same property name  
  @Consume reviewVotes: number;

  build() {  
    Column() {  
      Text(`Review Votes: ${this.reviewVotes}`)  
      Button(`Give +1 Vote`)  
        .onClick(() => this.reviewVotes += 1)  
    }  
    .width('50%')  
  }  
}  

@Component  
struct MiddleComp {  
  build() {  
    Row({ space: 5 }) {  
      DescendantComp()  
      DescendantComp()  
    }  
  }  
}  

@Component  
struct ParentComp {  
  build() {  
    MiddleComp()  
  }  
}  

@Entry  
@Component  
struct AncestorComp {  
  // @Provide provides the variable to descendant components  
  @Provide reviewVotes: number = 0;  
  build() {  
    Column() {  
      Button(`Ancestor: Give +1 Vote`)  
        .onClick(() => this.reviewVotes += 1)  
      ParentComp()  
    }  
  }  
}

@Observed/@ObjectLink Decorators

The @Observed and @ObjectLink decorators are used for two-way data synchronization involving nested objects or arrays. @Observed decorates a class to make its properties observable, while @ObjectLink creates a two-way binding to an instance of an @Observed class.

Nested Object Properties with @Observed/@ObjectLink


class NestedValue {  
  public id: number;  
  public value: number;

  constructor(value: number) {  
    this.id = Math.random();  
    this.value = value;  
  }  
}

@Observed  
class Container {  
  public nested: NestedValue;

  constructor(nested: NestedValue) {  
    this.nested = nested;  
  }  
}

@Component  
struct NestedView {  
  @ObjectLink nestedValue: NestedValue;  
  label: string = 'Nested View';

  build() {  
    Row() {  
      Button(`${this.label}: Value=${this.nestedValue.value} +1`)  
        .onClick(() => {  
          this.nestedValue.value += 1;  
        })  
    }  
  }  
}  

@Entry  
@Component  
struct ContainerView {  
  @State container: Container = new Container(new NestedValue(0));  
  build() {  
    Column() {  
      NestedView({ label: 'View #1', nestedValue: this.container.nested })  
      NestedView({ label: 'View #2', nestedValue: this.container.nested })  
      Button(`Container: Value+=1`)  
        .onClick(() => {  
          this.container.nested.value += 1;  
        })  
      Button(`Container: Replace Nested Value`)  
        .onClick(() => {  
          this.container.nested = new NestedValue(0);  
        })  
      Button(`Container: Replace Container`)  
        .onClick(() => {  
          this.container = new Container(new NestedValue(0));  
        })  
    }  
  }  
}

Application-Level State Management

LocalStorage: Page-Level UI State Storage

LocalStorage provides page-level UI state storage, typically used for state sharing within a UIAbility or between pages. It offers two decorators: @LocalStorageProp for one-way sync and @LocalStorageLink for two-way sync.

Using LocalStorage in Componants


// Create a new LocalStorage instance and initialize it  
let storage = new LocalStorage({ 'counterValue': 47 });  

@Component  
struct ChildComponent {  
  // @LocalStorageLink establishes two-way binding with 'counterValue' in LocalStorage  
  @LocalStorageLink('counterValue') counter: number = 1;  

  build() {  
    Button(`Child: Counter=${this.counter}`)  
      // Changes sync to LocalStorage and ParentComponent.counter  
      .onClick(() => this.counter += 1)  
  }  
}  

// Make LocalStorage accessible to the component  
@Entry(storage)  
@Component  
struct ParentComponent {  
  // @LocalStorageLink establishes two-way binding with 'counterValue' in LocalStorage  
  @LocalStorageLink('counterValue') counter: number = 1;  
  build() {  
    Column({ space: 15 }) {  
      Button(`Parent: Counter=${this.counter}`) // Initial value from LocalStorage will be 47  
        .onClick(() => this.counter += 1)  
      // ChildComponent automatically gets access to ParentComponent's LocalStorage instance  
      ChildComponent()  
    }  
  }  
}

AppStorage: Central Application State Storage

AppStorage is a special singleton LocalStorage object created by the UI framework when the application starts. It provides central storage for application UI state properties.

Using AppStorage with StorageLink and StorageProp


// Set or create a property in AppStorage  
AppStorage.SetOrCreate('theme', 'dark');  
AppStorage.SetOrCreate('fontSize', 16);

@Entry  
@Component  
struct SettingsPage {  
  @StorageLink('theme') currentTheme: string = 'light';  
  @StorageProp('fontSize') currentSize: number = 14;  

  build() {  
    Column() {  
      Text(`Current Theme: ${this.currentTheme}`)  
        .onClick(() => {  
          this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';  
        })  
      
      Text(`Current Font Size: ${this.currentSize}`)  
        .onClick(() => {  
          // This change won't sync back to AppStorage because @StorageProp is one-way  
          this.currentSize += 2;  
        })  
    }  
  }  
}

PersistentStorage: Persistent UI State

PersistentStorage allows you to persist selected AppStorage properties to device disk. It works in conjunction with AppStorage, with all property accesses going through AppStorage.

Using PersistentStorage with AppStorage


// First, define which properties should be persisted  
PersistentStorage.persistProp('username', 'default_user');  
PersistentStorage.persistProp('lastLogin', new Date().toISOString());

@Entry  
@Component  
struct ProfilePage {  
  @StorageLink('username') userName: string = 'Anonymous';  
  @StorageLink('lastLogin') lastLoginDate: string = '';

  build() {  
    Column() {  
      Text(`Username: ${this.userName}`)  
        .onClick(() => {  
          // Changes will persist to disk  
          this.userName = 'New_User_' + Math.floor(Math.random() * 1000);  
        })  
      
      Text(`Last Login: ${this.lastLoginDate}`)  
    }  
  }  
}

Environment Parameters

Environment parameters represent the runtime environment of the application and are synchronized to AppStorage. They can be used with AppStorage for context-aware UI behavior.

Using Environment Parameters


// Define environment parameters  
Environment.envProp('deviceType', 'phone');  
Environment.envProp('isDarkMode', false);

@Entry  
@Component  
struct DeviceAwareUI {  
  @StorageLink('deviceType') deviceType: string = 'unknown';  
  @StorageLink('isDarkMode') isDarkMode: boolean = false;

  build() {  
    Column() {  
      Text(`Device Type: ${this.deviceType}`)  
      
      if (this.deviceType === 'tablet') {  
        Text('Tablet Layout')  
      } else if (this.deviceType === 'phone') {  
        Text('Phone Layout')  
      } else {  
        Text('Unknown Device Layout')  
      }  
      
      Button(`Toggle Dark Mode: ${this.isDarkMode ? 'ON' : 'OFF'}`)  
        .onClick(() => {  
          this.isDarkMode = !this.isDarkMode;  
        })  
    }  
  }  
}

Tags: HarmonyOS ArkTS State Management @State @Prop

Posted on Thu, 03 Sep 2026 16:28:07 +0000 by benyboi