@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;
})
}
}
}