这是一个任务追踪器(Task Tracker)示例,它将涵盖以下 V2 状态管理装饰器:

  • @ObservedV2 + @Trace(深层观察)
  • @Local(组件自身状态)
  • @Param(父到子输入)
  • @Event(子到父事件)
  • @Monitor(监听变化)
  • @Computed(派生计算)
  • @Provider / @Consumer(跨组件共享)
  • @Once(一次性初始化)

效果

以下是基于示例项目 SampleComponentV2任务追踪器(Task Tracker) 的完整实现流程操作指南。示例代码位于 entry/src/main/ets/pages/Index.ets


一、概述

本示例通过一个完整的任务追踪器应用,演示了 ArkUI State Management V2 的全部核心装饰器:

装饰器 用途 示例中的使用位置
@ObservedV2 + @Trace 深度可观察数据模型 TaskItemTaskStatsModel
@ComponentV2 V2 组件声明 所有自定义组件
@Local 组件自身响应式状态 Index.tasksAddTaskInput.inputTitle
@Param 父→子单向输入 TaskCard.taskTaskFilter.filterType
@Event 子→父事件通知 TaskCard.onToggleCompleteTaskFilter.onFilterChange
@Once 仅首次初始化同步 PriorityBadge.priority
@Monitor 深度监听属性变化 TaskCard.onCompletedChange
@Computed 缓存派生计算值 TaskStatsModel.pendingCountcompletionRate
@Provider 向组件树发布状态 Index.statsModel
@Consumer 订阅祖先发布的状态 StatsPanel.statsModel

二、实现流程

步骤 1:定义深度可观察的数据模型

装饰器@ObservedV2 + @Trace

V1 使用 @Observed 装饰类,但只能观察一级属性变更;V2 的 @ObservedV2 配合 @Trace 可以实现属性级别的深度观察。

@ObservedV2
class TaskItem {
  @Trace id: string = '';
  @Trace title: string = '';
  @Trace completed: boolean = false;
  @Trace priority: number = 0; // 0=低, 1=中, 2=高

  constructor(id: string, title: string, priority: number = 0) {
    this.id = id;
    this.title = title;
    this.priority = priority;
  }
}

要点

  • @ObservedV2 必须与 @Trace 配合使用,单独使用 @ObservedV2 不产生可观察效果
  • @Trace 标记的属性变更会触发 UI 精确局部刷新,而非整个组件重绘
  • 构造函数中赋值 @Trace 属性是合法的

步骤 2:使用 @Computed 定义派生计算值

装饰器@Computed

@Computed 是 V2 新增的装饰器,用于定义只读的缓存派生属性。它仅在依赖的 @Trace 属性变化时重新计算,避免每次 build() 都重复运算。

@ObservedV2
class TaskStatsModel {
  @Trace total: number = 0;
  @Trace completedCount: number = 0;
  @Trace highPriorityCount: number = 0;

  @Computed
  get pendingCount(): number {
    return this.total - this.completedCount;
  }

  @Computed
  get completionRate(): string {
    if (this.total === 0) {
      return '0%';
    }
    return `${Math.round((this.completedCount / this.total) * 100)}%`;
  }
}

要点

  • @Computed 只能装饰 getter,不允许 setter
  • getter 内必须引用 @Trace 属性才能建立依赖关系
  • 计算结果被缓存,相同依赖值不会重复计算

步骤 3:创建 @Param + @Once 子组件

装饰器@Param + @Once

@Param 是 V2 的输入参数装饰器(替代 V1 的 @Prop)。@Once 修饰的 @Param 仅在组件首次创建时同步父组件数据,后续父组件变更不再更新。

@ComponentV2
struct PriorityBadge {
  @Once @Param priority: number = 0;

  build() {
    Text(this.getPriorityLabel())
      .fontSize(12)
      .fontColor(Color.White)
      .backgroundColor(this.getPriorityColor())
  }
}

要点

  • @Once 适用于"初始化后冻结"的场景,如优先级标签在创建时确定不再变更
  • 如果需要持续响应父组件变更,仅用 @Param 而不加 @Once
  • @Param 必须有默认值或与 @Require 配合强制要求外部传入

步骤 4:创建 @Param + @Event + @Monitor 子组件

装饰器@Param + @Event + @Monitor

这是 V2 组件的核心交互模式:

  • @Param 接收父组件传入的数据
  • @Event 定义回调方法,子组件通过调用它通知父组件
  • @Monitor 监听 @Param@Local 属性的变化
@ComponentV2
struct TaskCard {
  @Param task: TaskItem = new TaskItem('', '', 0);
  @Event onToggleComplete: (id: string) => void;
  @Event onDelete: (id: string) => void;

  @Monitor('task.completed')
  onCompletedChange(monitor: IMonitor) {
    const before = monitor.value()?.before ?? false;
    const after = monitor.value()?.now ?? false;
    console.info(`Task ${this.task.id} completed changed: ${before} -> ${after}`);
  }

  build() {
    Row({ space: 12 }) {
      Checkbox()
        .select(this.task.completed)
        .onChange(() => { this.onToggleComplete(this.task.id); })
      // ...
    }
  }
}

要点

  • @Param + @Event 组合替代了 V1 的 @Link 双向绑定。父组件通过 @Param 下发数据,通过 @Event 接收子组件的变更通知
  • @Monitor 是 V2 的 @Watch,支持监听 @Trace 属性的深层变化(如 'task.completed'
  • @Monitor 回调的 monitor 参数提供变更前后的值

步骤 5:实现 @Param + @Event 双向绑定模式

V2 中不再使用 @Link,双向绑定通过 @Param(读)+ @Event(写回)实现:

// 子组件:用 @Param 读取,用 @Event 写回
@ComponentV2
struct TaskFilter {
  @Param filterType: number = 0;
  @Event onFilterChange: (type: number) => void;

  build() {
    Row({ space: 8 }) {
      ForEach([0, 1, 2], (type: number) => {
        Button(this.getFilterLabel(type))
          .backgroundColor(this.filterType === type ? '#3498DB' : '#BDC3C7')
          .onClick(() => { this.onFilterChange(type); })
      }, (type: number) => type.toString())
    }
  }
}

// 父组件:传入 @Param 并监听 @Event
TaskFilter({
  filterType: this.filterType,
  onFilterChange: (type: number) => { this.filterType = type; }
})

要点

  • 数据流始终单向:父→子通过 @Param,子→父通过 @Event
  • 父组件在 @Event 回调中更新自己的 @Local 状态,然后 @Param 自动同步到子组件
  • 这比 V1 的 @Link 更显式,避免了隐式双向绑定的困惑

步骤 6:实现 @Provider + @Consumer 跨组件通信

装饰器@Provider + @Consumer

替代 V1 的 @Provide / @Consume,用于跨多层组件传递状态,无需逐层 props 传递。

// 父组件(发布者)
@Entry
@ComponentV2
struct Index {
  @Provider() statsModel: TaskStatsModel = new TaskStatsModel();
  // ...
}

// 深层子组件(订阅者)- 无需父组件显式传参
@ComponentV2
struct StatsPanel {
  @Consumer() statsModel: TaskStatsModel = new TaskStatsModel();

  build() {
    Row({ space: 16 }) {
      Text(`${this.statsModel.total}`)
      Text(`${this.statsModel.completedCount}`)
      Text(`${this.statsModel.pendingCount}`)      // 来自 @Computed
      Text(this.statsModel.completionRate)           // 来自 @Computed
    }
  }
}

要点

  • @Provider 的 key 默认为变量名(statsModel),@Consumer 通过同名 key 自动匹配
  • @Consumer 组件在父组件树中无需显式接收参数:StatsPanel() 即可
  • @Provider / @Consumer 支持双向同步:任一方修改都会同步到另一方

步骤 7:组装主页面

主页面使用 @Local 管理自身状态,@Provider 发布共享状态:

@Entry
@ComponentV2
struct Index {
  @Local tasks: TaskItem[] = [];
  @Local filterType: number = 0;
  @Local newTaskPriority: number = 0;
  @Local nextId: number = 1;
  @Provider() statsModel: TaskStatsModel = new TaskStatsModel();

  aboutToAppear(): void {
    this.tasks = [
      new TaskItem('1', '学习 ComponentV2 基础', 2),
      // ...
    ];
    this.updateStats();
  }

  private updateStats(): void {
    this.statsModel.total = this.tasks.length;
    this.statsModel.completedCount = this.tasks.filter(t => t.completed).length;
  }

  build() {
    Column({ space: 16 }) {
      StatsPanel()                          // @Consumer 自动订阅
      AddTaskInput({ ... })                 // @Param + @Event
      TaskFilter({ ... })                   // @Param + @Event 双向绑定
      List() {
        ForEach(this.getFilteredTasks(), (task: TaskItem) => {
          ListItem() {
            TaskCard({ task, onToggleComplete: ..., onDelete: ... })
          }
        }, (task: TaskItem) => task.id)     // 使用唯一 id 作为 key
      }
    }
  }
}

三、V1 与 V2 对照表

V1 模式 V2 模式 说明
@Component @ComponentV2 组件声明装饰器
@State @Local 组件自身响应式状态
@Prop @Param 父→子单向输入
@Link @Param + @Event 双向绑定改为显式单向+事件
@Watch @Monitor 属性变化监听
@Provide / @Consume @Provider / @Consumer 跨层级状态共享
@Observed + @ObjectLink @ObservedV2 + @Trace 深度可观察数据模型
computed getter @Computed 缓存派生计算值
@Reusable 不支持 @ComponentV2 不支持组件复用

四、注意事项

  1. **@ComponentV2 不支持 @Reusable**:V2 组件不能使用组件复用池。列表性能优化应使用 @ObservedV2 + @Trace 的精细响应式更新来减少不必要的重绘。

  2. **@Param + @Event 替代 @Link**:V2 取消了隐式双向绑定,所有子→父通信必须通过 @Event 回调。这是刻意的设计选择,使数据流更清晰可追踪。

  3. @Monitor 支持深层路径:可以监听 'task.completed' 这样的深层属性变更,而 V1 的 @Watch 只能监听变量本身。

  4. build() 应保持纯粹:不要在 build() 中执行副作用(网络请求、日志输出、状态修改)。副作用应放在 aboutToAppear()@Monitor 回调中。

  5. ForEach 必须提供稳定 key:使用对象唯一 id 作为 key 值,不要使用 index。本示例使用 (task: TaskItem) => task.id

  6. 命名空间避免与 struct 同名:辅助工具函数应提取到独立命名空间(如 PriorityUtils),不要与 struct PriorityBadge 使用相同名称。


五、项目文件结构

entry/src/main/ets/
  pages/
    Index.ets          ← 所有示例代码(单文件演示)
  entryability/
    EntryAbility.ets   ← 应用入口 Ability

本示例将所有组件放在同一文件中便于理解。在实际项目中,建议按职责拆分到独立文件:

entry/src/main/ets/
  models/
    TaskItem.ets       ← @ObservedV2 数据模型
    TaskStatsModel.ets ← @ObservedV2 + @Computed 统计模型
  components/
    TaskCard.ets       ← @Param + @Event + @Monitor
    TaskFilter.ets     ← @Param + @Event
    StatsPanel.ets     ← @Consumer
    PriorityBadge.ets  ← @Param + @Once
    AddTaskInput.ets   ← @Param + @Event + @Local
  pages/
    Index.ets          ← @Entry @Local @Provider 主页面

六、总结

实现了一个完整的任务追踪器,覆盖了 ComponentV2 全部 9 种核心装饰器:

装饰器 使用位置
@ObservedV2 + @Trace TaskItemTaskStatsModel 数据模型
@Local Index.tasksAddTaskInput.inputTitle
@Param TaskCard.taskTaskFilter.filterTypePriorityBadge.priority
@Event TaskCard.onToggleCompleteTaskFilter.onFilterChange
@Once PriorityBadge.priority(首次初始化后冻结)
@Monitor TaskCard.onCompletedChange(监听深层属性)
@Computed TaskStatsModel.pendingCountcompletionRate
@Provider / @Consumer Index.statsModel 发布 → StatsPanel 订阅
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐