After a period of development, ArkTS and ArkUI have moved beyond the stage of merely borrowing strengths from other frameworks. They are now evolving their own distinct styles and taking unique paths. One of the most critical systems in ArkUI's runtime—state management, which handles data and page interaction—has naturally undergone an upgrade from V1 to V2. To keep up with the latest trends, I’ve started a new project fully built using V2 state management. Below is a brief overview of the features I’ve used so far and the pitfalls I’ve encountered.
Differences Between State Management V1 and V2
State Management V1: Proxy Observation Pattern
Core Mechanism: When creating a state variable, a proxy observer is created simultaneously. This observer can only detect changes at the proxy level.
Usage Limitations:
- State variables are tightly coupled with the UI and cannot exist independently.
- When multiple views proxy the same data, changes made in one view do not notify the others to update.
- Observation is shallow—only first-level property changes are detected. Deep observation and listening are not supported.
State Management V2: Data Itself is Observable
Core Mechanism: The data itself becomes observable. Any data changes directly trigger the corresponding view updates.
Advantages:
- State variables are independent of the UI, and data changes are instantly reflected across all relevant views, ensuring data and UI are always synchronized.
- Supports deep observation and listening, with an optimized observation mechanism that maintains performance.
- Enables precise updates at the object property level and minimal updates for array elements, improving efficiency.
- Decorators are user-friendly and highly extensible. Input and output within components are clear, facilitating componentized development and maintenance.
State Variables
Let’s start with the basics—creating a state variable. In V1, this was done using @State
, but in V2, a new decorator @Local
is used:
@Local var: SomeType = SomeValue
Compared to @State
, @Local
does not allow external initialization, making its role as “internal state” clearer.
Additionally, with the new @ObservedV2
and @Trace
capabilities, observing internal state variables is simpler and no longer requires @ObjectLink
.
The @ObservedV2
and @Trace
decorators are used to decorate classes and their properties, enabling deep observation:
-
@ObservedV2
and@Trace
must be used together; using either one alone has no effect. - When a property decorated with
@Trace
changes, only components associated with that property are refreshed. - For nested classes, a property must be decorated with
@Trace
, and the nested class itself must be decorated with@ObservedV2
for UI updates to be triggered.
Example:
@ObservedV2
class Person {
@Trace age: number = 100;
}
@Entry
@ComponentV2
struct Index {
father: Person = new Person();
build() {
Column() {
Text(`${this.father.son.age}`)
.onClick(() => {
this.father.son.age++;
})
}
}
}
The combination of @ObservedV2
+ @Trace
supports:
- Nested classes
- Inherited classes
- Arrays of basic/object types
-
Map
andSet
types
However, it does not support types that require JSON.stringify
serialization.
External Initialization
It’s common for child components to require external parameters. In such cases, the @Param
decorator is used. It replaces the older @Prop
(initialization with copy) and @Link
(initialization with synchronization), unifying them into a one-way synchronization from parent to child. It also allows for local initialization. To enforce required parameters, add the @Require
decorator.
Example of a custom component with parameter initialization:
@ComponentV2
export struct ActionButton {
@Param @Require icon: Resource
@Param @Require clickAction: () => void
build() {
Button() {
SymbolGlyph(this.icon)
.fontSize(24)
.fontColor([$r('sys.color.font_primary')])
}.onClick(() => { this.clickAction(); })
.backgroundColor($r('sys.color.comp_background_secondary'))
.padding(8)
.clickEffect({
level: ClickEffectLevel.LIGHT
})
}
}
Usage is the same as in V1.
To support one-time initialization without responding to future changes from the parent, V2 introduces the @Once
decorator. Use it with @Param
to intercept parent data changes.
Two-Way Synchronization
To achieve synchronization from child to parent or bi-directional syncing, V2 provides two mechanisms:
@ Provider and @Consumer: Cross-Component Hierarchical Synchronization
The new provider mechanism is similar to the original @Provide
, but with enhanced capabilities:
-
@Consumer
allows local initialization with default values if no matching@Provider
is found. -
@Consumer
supports function types. -
@Provider
and@Consumer
support overloading—multiple@Provider
s with the same name are allowed, and@Consumer
will look up the nearest@Provider
.
@Event Decorator: Standardized Component Output
The @Event
decorator enables child components to update parent variables. When the parent variable is also the data source for the child’s @Param
, changes will be synced back. Since I personally don’t use this pattern much, I won’t elaborate here.
AppStorageV2
The usage of AppStorageV2 is further simplified compared to V1. It no longer uses @StorageProp
and @StorageLink
. Instead, it adopts a global singleton pattern:
@Local windowAvoidance: WindowAvoidanceData =
AppStorageV2.connect(WindowAvoidanceData, SCApp.windowAvoidance, () => new WindowAvoidanceData(0, 0))!
In this example (used to record window avoidance values):
- The first parameter is the type.
- The second is the alias.
- The third is the default initialization function.
Since the return value is nullable but we provided a default initializer, we can safely append the !
.
Top comments (0)