Skip to content

状态管理

概述

状态管理是框架的核心能力之一,提供跨页面、跨模块的响应式状态共享机制。本框架强制使用 HarmonyOS V2 版本的状态管理 API(@ObservedV2AppStorageV2PersistenceV2),禁用所有 V1 版本 API。

核心特性

  • 响应式更新:状态变化自动触发 UI 刷新,无需手动调用更新方法
  • 全局共享:多个页面可以共享同一状态实例,实现数据同步
  • 持久化支持:支持将状态持久化到本地存储,应用重启后自动恢复
  • 类型安全:完整的 TypeScript 类型支持,编译时类型检查

适用场景

  • 用户信息:登录状态、用户资料等需要全局访问的数据
  • 应用配置:主题设置、语言偏好等应用级配置
  • 业务状态:购物车、消息未读数等跨页面共享的业务数据
  • 临时缓存:页面间传递的临时数据

核心装饰器

@ObservedV2

用于标记可观察的类,使其成为响应式状态容器。

typescript
@ObservedV2
export class CounterState {
  @Trace
  count: number = 0;
}

使用规则

  • 必须用于状态类的类声明
  • 类中需要响应式的字段必须使用 @Trace 装饰
  • 支持嵌套对象,嵌套对象也需要使用 @ObservedV2 装饰

@Trace

用于标记需要追踪的响应式字段,只有标注 @Trace 的字段变化才会触发 UI 更新。

typescript
@ObservedV2
export class UserState {
  @Trace
  userName: string = "";  // 会触发 UI 更新

  userId: number = 0;     // 不会触发 UI 更新
}

使用规则

  • 只能用于 @ObservedV2 类的字段
  • 基本类型(string、number、boolean)直接使用 @Trace
  • 复杂类型(对象、数组)需要配合 @Type 使用

@Type

用于标注复杂类型字段,确保序列化和反序列化正确。

typescript
@ObservedV2
export class UserState {
  @Type(User)
  @Trace
  userInfo: User = new User();

  @Type(Auth)
  @Trace
  auth: Auth = new Auth();
}

AppStorageV2:临时全局状态

AppStorageV2 用于存储应用运行期间的临时全局状态,应用关闭后数据会丢失。

基本用法

1. 定义状态类

typescript
import { AppStorageV2 } from "@kit.ArkUI";

/**
 * AppStorageV2 键名
 */
export const DEMO_COUNTER_KEY: string = "demo_counter_state";

/**
 * 全局计数器状态
 */
@ObservedV2
export class DemoCounterState {
  /**
   * 当前计数
   */
  @Trace
  count: number = 0;

  /**
   * 计数加一
   */
  increment(step: number = 1): void {
    this.count += step;
  }

  /**
   * 计数减一
   */
  decrement(step: number = 1): void {
    if (this.count - step < 0) {
      this.count = 0;
      return;
    }
    this.count -= step;
  }

  /**
   * 重置计数
   */
  reset(resetValue: number = 0): void {
    this.count = resetValue;
  }
}

2. 创建获取函数

typescript
/**
 * 获取全局计数器状态实例;若不存在则创建
 */
export function getDemoCounterState(): DemoCounterState {
  return AppStorageV2.connect<DemoCounterState>(
    DemoCounterState,
    DEMO_COUNTER_KEY,
    () => new DemoCounterState()
  )!;
}

3. 在 ViewModel 中使用

typescript
@ObservedV2
export default class StateManagementViewModel extends BaseViewModel {
  /**
   * 计数器状态
   */
  @Trace
  counterState: DemoCounterState = getDemoCounterState();

  /**
   * 计数加一
   */
  increment(): void {
    this.counterState.increment();
  }
}

4. 在 View 中使用

typescript
@ComponentV2
export struct StateManagementPage {
  @Local
  private vm: StateManagementViewModel = new StateManagementViewModel();

  build() {
    Column() {
      // 显示计数
      Text(`${this.vm.counterState.count}`)
        .fontSize(48)
        .fontWeight(FontWeight.Bold);

      // 增加按钮
      Button("增加")
        .onClick(() => {
          this.vm.increment();
        });
    }
  }
}

AppStorageV2 API

typescript
// 连接或创建状态
AppStorageV2.connect<T>(
  type: Type<T>,           // 状态类类型
  key: string,             // 唯一键名
  defaultCreator: () => T  // 默认创建函数
): T | undefined

// 移除状态
AppStorageV2.remove(key: string): void

// 清空所有状态
AppStorageV2.clear(): void

PersistenceV2:持久化全局状态

PersistenceV2 用于存储需要持久化的全局状态,应用关闭后数据会保存到本地,下次启动时自动恢复。

基本用法

1. 定义状态类

typescript
import { PersistenceV2, Type } from "@kit.ArkUI";
import { contextConstant } from "@kit.AbilityKit";

/**
 * PersistenceV2 键名
 */
export const USER_STATE_KEY: string = "user_state";

/**
 * 全局用户状态
 */
@ObservedV2
export class UserState {
  /**
   * 认证信息
   */
  @Type(Auth)
  @Trace
  private auth: Auth = new Auth();

  /**
   * 用户信息
   */
  @Type(User)
  @Trace
  userInfo: User = new User();

  /**
   * 更新用户登录状态
   */
  updateUserState(auth: Auth, user: User): void {
    this.auth = Auth.fromResponse(auth);
    this.userInfo = User.fromResponse(user);
    this.persist();
  }

  /**
   * 用户登出,清空状态
   */
  logout(): void {
    this.auth = new Auth();
    this.userInfo = new User();
    PersistenceV2.remove(USER_STATE_KEY);
  }

  /**
   * 持久化当前用户状态
   */
  private persist(): void {
    PersistenceV2.save(USER_STATE_KEY);
  }
}

2. 创建获取函数

typescript
/**
 * 获取或创建全局用户状态(持久化)
 */
export function getUserState(): UserState {
  return PersistenceV2.globalConnect<UserState>({
    type: UserState,
    key: USER_STATE_KEY,
    defaultCreator: () => new UserState(),
    areaMode: contextConstant.AreaMode.EL3  // 加密存储区域
  })!;
}

// 监听序列化错误
PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
  console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`);
});

3. 在业务代码中使用

typescript
// 登录成功后保存用户状态
const userState = getUserState();
userState.updateUserState(authData, userData);

// 获取用户信息
const user = getUserState().getUserInfo();

// 登出
getUserState().logout();

PersistenceV2 API

typescript
// 全局连接或创建持久化状态
PersistenceV2.globalConnect<T>(options: {
  type: Type<T>,                              // 状态类类型
  key: string,                                // 唯一键名
  defaultCreator: () => T,                    // 默认创建函数
  areaMode?: contextConstant.AreaMode         // 存储区域模式
}): T | undefined

// 保存状态到本地
PersistenceV2.save(key: string): void

// 移除持久化状态
PersistenceV2.remove(key: string): void

// 监听序列化错误
PersistenceV2.notifyOnError(
  callback: (key: string, reason: string, msg: string) => void
): void

存储区域模式

typescript
contextConstant.AreaMode.EL1  // 公共区域,未加密
contextConstant.AreaMode.EL2  // 私有区域,设备级加密
contextConstant.AreaMode.EL3  // 私有区域,用户级加密(推荐)
contextConstant.AreaMode.EL4  // 私有区域,仅在解锁时可访问

推荐使用 EL3:用户级加密,安全性高,适合存储敏感数据。

在 MVVM 架构中使用

架构层次

View (视图层)
  ↓ @Local 绑定
ViewModel (视图模型层)
  ↓ @Trace 引用
State (状态层)
  ↓ AppStorageV2/PersistenceV2
Global Storage (全局存储)

完整示例

1. 定义全局状态(shared/state)

typescript
// shared/state/src/main/ets/ThemeState.ets
import { AppStorageV2 } from "@kit.ArkUI";

export const THEME_STATE_KEY: string = "theme_state";

@ObservedV2
export class ThemeState {
  @Trace
  isDarkMode: boolean = false;

  toggleTheme(): void {
    this.isDarkMode = !this.isDarkMode;
  }
}

export function getThemeState(): ThemeState {
  return AppStorageV2.connect<ThemeState>(
    ThemeState,
    THEME_STATE_KEY,
    () => new ThemeState()
  )!;
}

2. 在 ViewModel 中使用(packages/xxx/viewmodel)

typescript
// packages/settings/src/main/ets/viewmodel/SettingsViewModel.ets
import { BaseViewModel } from "@core/base";
import { ThemeState, getThemeState } from "@shared/state";

@ObservedV2
export default class SettingsViewModel extends BaseViewModel {
  @Trace
  themeState: ThemeState = getThemeState();

  /**
   * 切换主题
   */
  toggleTheme(): void {
    this.themeState.toggleTheme();
  }

  /**
   * 获取当前主题模式
   */
  getCurrentTheme(): string {
    return this.themeState.isDarkMode ? "深色模式" : "浅色模式";
  }
}

3. 在 View 中使用(packages/xxx/view)

typescript
// packages/settings/src/main/ets/view/SettingsPage.ets
import SettingsViewModel from "../viewmodel/SettingsViewModel";

@ComponentV2
export struct SettingsPage {
  @Local
  private vm: SettingsViewModel = new SettingsViewModel();

  build() {
    Column() {
      Text(`当前主题:${this.vm.getCurrentTheme()}`)
        .fontSize(16);

      Button("切换主题")
        .onClick(() => {
          this.vm.toggleTheme();
        });
    }
  }
}

最佳实践

1. 状态类设计原则

typescript
// ✅ 推荐:单一职责,状态类只负责状态管理
@ObservedV2
export class CartState {
  @Trace
  items: CartItem[] = [];

  addItem(item: CartItem): void {
    this.items = [...this.items, item];  // 不可变更新
  }

  removeItem(id: string): void {
    this.items = this.items.filter(item => item.id !== id);
  }
}

// ❌ 避免:在状态类中处理复杂业务逻辑
@ObservedV2
export class CartState {
  async checkout(): Promise<void> {
    // 不应该在状态类中直接调用 API
    await api.checkout();
  }
}

2. 不可变更新

typescript
// ✅ 推荐:使用不可变方式更新数组和对象
@ObservedV2
export class TodoState {
  @Trace
  todos: Todo[] = [];

  addTodo(todo: Todo): void {
    this.todos = [...this.todos, todo];  // 创建新数组
  }

  updateTodo(id: string, updates: Partial<Todo>): void {
    this.todos = this.todos.map(todo =>
      todo.id === id ? { ...todo, ...updates } : todo
    );
  }
}

// ❌ 避免:直接修改原数组
addTodo(todo: Todo): void {
  this.todos.push(todo);  // 可能不会触发 UI 更新
}

3. 复杂类型处理

typescript
// ✅ 推荐:使用 @Type 装饰器标注复杂类型
@ObservedV2
export class OrderState {
  @Type(Order)
  @Trace
  currentOrder: Order = new Order();

  @Type(OrderItem)
  @Trace
  items: OrderItem[] = [];
}

// 确保嵌套类也使用 @ObservedV2
@ObservedV2
export class Order {
  @Trace
  orderId: string = "";

  @Trace
  totalAmount: number = 0;
}

4. 状态持久化时机

typescript
@ObservedV2
export class UserState {
  @Type(User)
  @Trace
  userInfo: User = new User();

  // ✅ 推荐:在状态变更后立即持久化
  updateUserInfo(user: User): void {
    this.userInfo = User.fromResponse(user);
    this.persist();  // 立即保存
  }

  private persist(): void {
    PersistenceV2.save(USER_STATE_KEY);
  }
}

5. 状态访问封装

typescript
// ✅ 推荐:提供便捷的访问方法
@ObservedV2
export class UserState {
  @Type(Auth)
  @Trace
  private auth: Auth = new Auth();

  // 提供公共访问方法
  getToken(): string | null {
    return this.auth?.token ?? null;
  }

  isLoggedIn(): boolean {
    return !!this.getToken();
  }

  hasValidToken(): boolean {
    return !!this.auth?.token && !this.auth.isExpired();
  }
}

常见问题

1. 状态更新了但 UI 没有刷新

原因:字段没有使用 @Trace 装饰器。

typescript
// ❌ 错误:缺少 @Trace
@ObservedV2
export class CounterState {
  count: number = 0;  // UI 不会更新
}

// ✅ 正确:添加 @Trace
@ObservedV2
export class CounterState {
  @Trace
  count: number = 0;  // UI 会更新
}

2. 数组或对象更新后 UI 没有刷新

原因:直接修改了原对象,而不是创建新对象。

typescript
// ❌ 错误:直接修改原数组
addItem(item: string): void {
  this.items.push(item);  // 可能不会触发更新
}

// ✅ 正确:创建新数组
addItem(item: string): void {
  this.items = [...this.items, item];
}

3. 持久化失败

原因:复杂类型没有使用 @Type 装饰器。

typescript
// ❌ 错误:缺少 @Type
@ObservedV2
export class UserState {
  @Trace
  userInfo: User = new User();  // 序列化可能失败
}

// ✅ 正确:添加 @Type
@ObservedV2
export class UserState {
  @Type(User)
  @Trace
  userInfo: User = new User();
}

4. 多个页面状态不同步

原因:每个页面创建了独立的状态实例,而不是使用全局状态。

typescript
// ❌ 错误:每次都创建新实例
@ObservedV2
export class MyViewModel extends BaseViewModel {
  @Trace
  counterState: DemoCounterState = new DemoCounterState();  // 独立实例
}

// ✅ 正确:使用全局状态
@ObservedV2
export class MyViewModel extends BaseViewModel {
  @Trace
  counterState: DemoCounterState = getDemoCounterState();  // 全局共享
}

5. 状态类中的方法无法访问

原因:状态类的方法没有正确绑定 this

typescript
// ✅ 推荐:使用箭头函数或在调用时绑定 this
@ObservedV2
export class CounterState {
  @Trace
  count: number = 0;

  // 方法 1:使用普通方法(推荐)
  increment(): void {
    this.count += 1;
  }

  // 方法 2:使用箭头函数
  decrement = (): void => {
    this.count -= 1;
  };
}

官方文档

相关文档