鸿蒙开发:状态管理 V2 装饰器与组件说明
MVVM V2 文档里的核心装饰器(@Local、@Param、@Once、@Event、@ObservedV2、@Trace、@Type、@Monitor、@Computed、@Builder)与能力(Repeat、AppStorageV2、PersistenceV2)本工程均已使用;子组件用 @Param 声明的变量接收父组件传入的值,默认不能在子组件里修改(若允许“本地改一次”需配合 @On

一、类与属性级
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 有两种用法:非懒加载(本工程已用 |
|
@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)本工程均已使用;上述三项为“文档有提、工程未用”的扩展点。
八、参考
更多推荐




所有评论(0)