一、类与属性级

1. @ObservedV2

作用:标记一个为“可观测类”。只有被 @ObservedV2 装饰的类,其内部用 @Trace 装饰的属性变化才会被框架追踪,从而驱动 UI 更新。

用在:ViewModel 类、需要跨页/持久化的数据类(如 Setting)。

示例

@ObservedV2
export default class StudentViewModel {
  @Trace public name: string = '';
  @Trace public age: number = 0;
}

注意:@ObservedV2 只装饰类,不装饰变量或方法。


2. @Trace

作用:标记类中的属性为“可观测属性”。该属性一旦被修改,所有依赖它的 UI 会重新计算并刷新。

用在:@ObservedV2 类内部的成员变量(基本类型或数组等)。

示例

@ObservedV2
export default class StudentViewModel {
  @Trace public name: string = '';   // 变化 → 界面更新
  @Trace public age: number = 0;
  @Trace public remark: string = '';
}

注意:数组里若装的是自定义类,需配合 @Type 使用(见下)。


3. @Type(类名)

作用:声明某个 @Trace 数组里元素的类型。框架据此做两件事:
① 深层观测数组里每个对象的 @Trace 属性;
② 持久化(PersistenceV2)时正确序列化/反序列化。

用在@Trace public xxx: SomeViewModel[] 这类数组属性上。

示例

@ObservedV2
export default class StudentListViewModel {
  @Type(StudentViewModel)
  @Trace public students: StudentViewModel[] = [];
}

注意:@Type 与 @Trace 一起用在“可观测类的数组”上,缺一可能导致深层不更新或持久化异常。


二、组件状态与传参

4. @Local

作用:表示组件内部状态。变量由当前组件持有,变化时只刷新本组件(及依赖该状态的子组件)。

用在:页面或自定义组件里“仅本组件关心”的数据,如输入框内容、是否展开等。

示例

@ComponentV2
struct BottomView {
  @Local inputName: string = '';   // 仅本组件用,不传给父
  @Local inputAge: string = '';
  @Local inputGender: string = '男';
}

与 @Param 区别:@Local 是组件自己的状态;@Param 是父组件传进来的。


5. @Param

作用:表示父组件传入的只读参数。子组件用 @Param 声明的变量接收父组件传入的值,默认不能在子组件里修改(若允许“本地改一次”需配合 @Once)。

用在:子组件需要展示或依赖父组件给的数据时。

示例

@ComponentV2
struct TitleView {
  @Param setting: Setting = new Setting();   // 父组件传入
  @Param totalCount: number = 0;              // 父组件传入
}

父组件用法

TitleView({ setting: this.setting, totalCount: this.totalCount })

6. @Param @Once

作用(以官方文档为准):

  • “Once”指:父→子只同步一次。变量在初始化时接受父组件传入的值,之后父组件再改这个数据,不会同步给子组件(子保持当时拿到的值)。
  • 子组件:可以在本地多次修改该变量,并触发 UI 刷新,效果类似 @Local,但还能接收父传入的初始值。

所以 @Param @Once 不是“子组件只能改一次”,而是“从父只接收一次,子可随意改”。

本工程演示(主页面顶部):父有 @Local parentNum,按钮「父+1」让父数字递增;同一数据传给两个子组件:

  • 子(Param):仅 @Param value,父改则子跟着变(0→1→2→3…)。
  • 子(Once)@Param @Once value,只同步初始值,父改后子始终显示 0

注意

  • 若需要父一改、子就跟变,用普通 @Param 即可,不要加 @Once。

7. @Event

作用:声明子组件向父组件“发事件”的回调。子组件在合适时机(如点击删除)调用该回调,由父组件决定具体逻辑(如从列表移除)。

用在:子组件需要通知父组件做某件事时(删除、选中、提交等)。

示例

// 子组件
@ComponentV2
struct StudentItem {
  @Event onDelete: () => void = () => {};   // 父组件传入的回调
  // 点击删除时: this.onDelete();
}

// 父组件
StudentItem({
  student: obj.item,
  onDelete: () => this.studentList.removeStudent(obj.item)
})

三、计算与监听

8. @Computed

作用:标记一个 getter 为“计算属性”。框架会记录 getter 里用到的可观测状态(依赖),依赖不变时用缓存;依赖一变就重新计算,并刷新所有使用该 getter 的 UI。

用在:由其它状态“算出来”的值(如列表长度、未完成数量、合计等)。

示例

@Computed
get totalCount(): number {
  return this.studentList.students.length;   // 依赖 students
}
// 增删学生后,totalCount 自动变,“共 X 人”自动更新

注意:getter 内不要写副作用(如改其它状态),只做纯计算。


9. @Monitor('路径')

作用监听某个可观测属性的变化,在变化时执行你写的方法(如打日志、上报、同步到别处)。路径用字符串表示,如 'student.name''student.remark'

用在:需要“属性一变就执行一段逻辑”时,而不是只刷新 UI。

示例

@Monitor('student.name')
onNameChange(mon: IMonitor) {
  hilog.info(0x0000, 'StudentList', '姓名: %{public}s -> %{public}s',
    String(mon.value()?.before), String(mon.value()?.now));
}

@Monitor('student.remark')
onRemarkChange(mon: IMonitor) {
  hilog.info(0x0000, 'StudentList', '备注: %{public}s -> %{public}s',
    String(mon.value()?.before), String(mon.value()?.now));
}

注意:只有该路径对应的属性真的发生变化时才会触发;若界面没有修改该属性的操作,不会打印。


四、UI 复用与列表

10. @Builder

作用:把一段 UI 结构抽成可复用的“构建函数”,多处调用,减少重复代码。

用在:多个地方用到的相同或相似 UI(如统一风格的按钮、卡片、表单项)。

示例

@Builder
function ActionButton(label: string, onClick: () => void) {
  Button(label)
    .width('100%')
    .onClick(onClick)
}

// 使用
ActionButton('设置', () => { ... })
ActionButton('添加', () => { ... })

11. Repeat

作用:根据数组按项渲染多个子组件,类似“列表循环”。数据源变化时,框架会做增量更新,只更新变化的那几项。

用在:列表、表格行、标签组等“数据驱动的一串子组件”。

示例

Repeat<StudentViewModel>(this.studentList.students)
  .each((obj: RepeatItem<StudentViewModel>) => {
    StudentItem({
      student: obj.item,
      onDelete: () => this.studentList.removeStudent(obj.item)
    })
  })

注意:本工程中 Repeat.each() 只接受一个参数(构建函数),不支持 keyGenerator 等额外参数。


五、全局与持久化

12. AppStorageV2

作用应用内全局状态。通过 AppStorageV2.connect(类, 键, 初始化函数) 拿到或创建“单例”,多个页面/Ability 共用同一份数据,改一处处处生效。

用在:跨页面、跨 Ability 的配置或状态(如主题、语言、班级名称、是否显示某类数据)。

示例

// 定义可观测类
@ObservedV2
export class Setting {
  @Trace public className: string = '一年级1班';
}

// 在任意页面连接(无则创建)
@Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;

// 设置页改 className,主页面标题会同步更新

注意:数据只在进程存活期间存在,进程结束就清空;要关应用仍保留需用 PersistenceV2。


13. PersistenceV2

作用:把可观测类的实例持久化到本地,应用退出再打开后,通过 PersistenceV2.connect 取回上次的数据。

用在:需要“关应用再打开仍保留”的列表、表单草稿、用户设置等。

示例

@Local studentList: StudentListViewModel =
  PersistenceV2.connect(StudentListViewModel, 'StudentList', () => new StudentListViewModel())!;

注意

  • 恢复出来的可能是“普通对象”而非 ViewModel 实例,直接绑定到 UI 可能不刷新。本工程在 aboutToAppear 里做了 rehydrate:若检测到是普通对象,则转成新的 ViewModel 实例再赋回列表。
  • 持久化类需用 @ObservedV2,数组元素类型需用 @Type 声明。

六、本工程中的对应关系速查

装饰器/组件

本工程中的使用位置

@ObservedV2

StudentViewModel、StudentListViewModel、Setting

@Trace

name/age/gender/remark、students、className

@Type

StudentListViewModel.students

@Local

StudentListPage(studentList, setting, parentNum)、BottomView(输入框)、SettingPage(setting)

@Param

主页面 ParamDemoChild(value) 与 OnceDemoChild 对比、TitleView、ListView、StudentItem、BottomView

@Param @Once

主页面 OnceDemoChild(value) 与 ParamDemoChild 对比

@Event

StudentItem(onDelete)

@Computed

StudentListPage(totalCount)

@Monitor

StudentItem(onNameChange, onRemarkChange)

@Builder

BottomView(ActionButton)

Repeat

ListView(学生列表)

AppStorageV2

Setting(班级名称跨页)

PersistenceV2

StudentListViewModel(学生列表持久化)


七、文档中提到但本工程未使用的 V2 相关能力

对照 MVVM 模式(V2) 与状态管理 V2 文档,以下能力在文档中有提及,本工程当前未使用,便于后续扩展时参考:

能力

说明

Repeat 懒加载场景(LazyForEach)

文档中 Repeat 有两种用法:非懒加载(本工程已用 Repeat.each)与懒加载。懒加载适用于长列表、按需渲染,需配合 LazyForEach + 数据源接口使用,本工程列表较短,仅用非懒加载。

@Require

与 @Param 搭配表示必填参数,父组件未传时编译/运行会报错。本工程所有 @Param 均提供了默认值,未使用 @Require。

@Provider / @Consume

状态管理 V2 中的跨层级双向同步方案(祖先 ↔ 后代),与 AppStorageV2 不同。本工程跨页/跨 Ability 用 AppStorageV2,未使用 @Provider/@Consume。

小结:MVVM V2 文档里的核心装饰器(@Local、@Param、@Once、@Event、@ObservedV2、@Trace、@Type、@Monitor、@Computed、@Builder)与能力(Repeat、AppStorageV2、PersistenceV2)本工程均已使用;上述三项为“文档有提、工程未用”的扩展点。


八、参考

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐