任务追踪器(Task Tracker)
这是一个任务追踪器(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 |
深度可观察数据模型 | TaskItem、TaskStatsModel |
@ComponentV2 |
V2 组件声明 | 所有自定义组件 |
@Local |
组件自身响应式状态 | Index.tasks、AddTaskInput.inputTitle |
@Param |
父→子单向输入 | TaskCard.task、TaskFilter.filterType |
@Event |
子→父事件通知 | TaskCard.onToggleComplete、TaskFilter.onFilterChange |
@Once |
仅首次初始化同步 | PriorityBadge.priority |
@Monitor |
深度监听属性变化 | TaskCard.onCompletedChange |
@Computed |
缓存派生计算值 | TaskStatsModel.pendingCount、completionRate |
@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 不支持组件复用 |
四、注意事项
-
**
@ComponentV2不支持@Reusable**:V2 组件不能使用组件复用池。列表性能优化应使用@ObservedV2+@Trace的精细响应式更新来减少不必要的重绘。 -
**
@Param+@Event替代@Link**:V2 取消了隐式双向绑定,所有子→父通信必须通过@Event回调。这是刻意的设计选择,使数据流更清晰可追踪。 -
@Monitor支持深层路径:可以监听'task.completed'这样的深层属性变更,而 V1 的@Watch只能监听变量本身。 -
build()应保持纯粹:不要在build()中执行副作用(网络请求、日志输出、状态修改)。副作用应放在aboutToAppear()或@Monitor回调中。 -
ForEach必须提供稳定 key:使用对象唯一id作为 key 值,不要使用index。本示例使用(task: TaskItem) => task.id。 -
命名空间避免与 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 |
TaskItem、TaskStatsModel 数据模型 |
@Local |
Index.tasks、AddTaskInput.inputTitle |
@Param |
TaskCard.task、TaskFilter.filterType、PriorityBadge.priority |
@Event |
TaskCard.onToggleComplete、TaskFilter.onFilterChange |
@Once |
PriorityBadge.priority(首次初始化后冻结) |
@Monitor |
TaskCard.onCompletedChange(监听深层属性) |
@Computed |
TaskStatsModel.pendingCount、completionRate |
@Provider / @Consumer |
Index.statsModel 发布 → StatsPanel 订阅 |
更多推荐




所有评论(0)