✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃
鸿蒙 ArkTS 状态管理 V2 带来了一系列全新装饰器,不仅解决了 V1 版本中装饰器混淆、观测能力有限等问题,还强化了语义化和性能优化。本文将详细拆解@Local、@Param、@ObservedV2等核心装饰器,结合实战代码和对比表格,帮你快速上手 V2 版本的状态管理方案。
一、核心升级:为什么要用状态管理 V2?
状态管理 V1 存在诸多痛点:
- 装饰器类型多(@State/@Prop/@Link等),用法差异大,易混淆;
- 观测能力有限,无法深度监听对象嵌套属性变化;
- 组件内部状态易被外部篡改,语义不清晰;
- 跨组件传值规则复杂,性能优化成本高。
而 V2 版本通过统一装饰器语义、增强观测能力、简化传值逻辑,彻底解决了这些问题,是鸿蒙开发的必学技能。
二、核心装饰器详解(含实战代码)
❶ @ObservedV2/@Trace:属性深度侦听能力
用于实现对象 / 类属性的深度观测,解决 V1 中无法监听嵌套属性变化的问题。
- @ObservedV2:装饰类,标记该类支持深度观测;
- @Trace:装饰类中的属性,标记该属性需要被深度监听。
实战代码
// 装饰类,支持深度观测
@ObservedV2
export class GoodsItemType {
title: string = '';
@Trace price: number = 0; // 标记price属性需要深度监听
constructor(title: string, price: number) {
this.title = title;
this.price = price;
}
}
@Entry
@Component
struct Index {
@State goods: GoodsItemType[] = [
new GoodsItemType('商品1', 99),
new GoodsItemType('商品2', 199)
];
build() {
Column() {
// 实时响应price属性变化
Text(`商品1价格:${this.goods[0].price}`).fontSize(30);
Button('价格+1').onClick(() => {
this.goods[0].price++; // 触发@Trace深度监听,UI自动刷新
});
}
}
}
❷ @Local:组件内部状态(替代 V1 @State)
专门用于标记组件内部私有状态,禁止外部初始化,语义更清晰,是 V2 中组件内部状态的首选装饰器。
核心特性
- 必须在组件内部初始化,无法从外部传入;
- 支持基本类型(number/string/boolean)、对象、数组、Set/Map 等;
- 深度观测需配合@ObservedV2/@Trace。
实战代码
@ObservedV2
class Stu {
@Trace money: number = 1; // 深度监听money属性
}
@Entry
@ComponentV2
struct Index {
// 基本类型内部状态
@Local num: number = 11;
// 对象数组内部状态(配合@ObservedV2深度观测)
@Local school: Stu[] = [new Stu(), new Stu()];
build() {
Column() {
Text(`num: ${this.num}`).fontSize(24);
Button("num++").onClick(() => this.num++); // 触发UI刷新
Text(`学生1零花钱:${this.school[0].money}`).fontSize(24);
Button("零花钱+1").onClick(() => this.school[0].money++); // 深度监听,触发UI刷新
}
.padding(20)
}
}
@Local vs @State 核心对比
| 区别 | @State | @Local |
|---|---|---|
| 外部初始化 | 允许 | 禁止(组件内部私有) |
| 观测能力 | 仅支持一层属性监听 | 基础监听 +@Trace深度监听 |
| 语义化 | 模糊(可内部 / 外部初始化) | 清晰(仅内部状态) |
| 适用场景 | V1 遗留项目 | V2 新项目组件内部状态(首选) |
❸ @Param:组件外部输入(统一 V1 @Prop/@Link 等)
用于接收父组件传入的外部参数,统一了 V1 中@Prop/@Link/@ObjectLink等装饰器,用法更简单。
核心特性
- 单向同步:父组件数据变化同步到子组件,子组件不可直接修改;
- 支持本地初始化(可选),必填参数需配合@Require;
- 支持所有基本类型和复杂类型。
实战代码
// 子组件:接收外部传入参数
@ComponentV2
struct ChildComponent {
// 可选参数:本地初始化默认值
@Param title: string = "默认标题";
// 必填参数:必须从外部传入(配合@Require)
@Require @Param fontColor: Color;
build() {
Text(this.title)
.fontColor(this.fontColor)
.fontSize(24);
}
}
// 父组件:传递参数给子组件
@Entry
@ComponentV2
struct ParentComponent {
@Local parentTitle: string = "父组件传递的标题";
@Local parentColor: Color = Color.Blue;
build() {
Column() {
ChildComponent({
title: this.parentTitle,
fontColor: this.parentColor
});
}
}
}
❹ @Once:初始化同步一次(配合 @Param)
用于标记仅初始化一次的参数,父组件后续数据变化不会同步到子组件,适合静态配置类参数。
实战代码
@ComponentV2
struct ChildComponent {
// 仅初始化一次,父组件后续修改不生效
@Param @Once onceParam: string = "";
build() {
Text(`静态参数:${this.onceParam}`).fontSize(24);
}
}
@Entry
@ComponentV2
struct ParentComponent {
@Local message: string = "初始化值";
build() {
Column() {
Text(`父组件参数:${this.message}`).fontSize(24);
Button("修改父组件参数").onClick(() => {
this.message = "修改后的值"; // 子组件onceParam不会同步更新
});
ChildComponent({ onceParam: this.message });
}
}
}
❺ @Event:子组件修改父组件数据(配合 @Param)
由于@Param子组件不可直接修改,@Event用于装饰回调方法,实现子组件主动触发父组件数据修改,完成 “子→父” 通信。
实战代码
@Entry
@ComponentV2
struct Parent {
@Local title: string = "初始标题";
@Local fontColor: Color = Color.Red;
build() {
Column() {
Child({
title: this.title,
fontColor: this.fontColor,
// 传递回调方法给子组件
changeTitle: (newTitle: string) => {
this.title = newTitle; // 子组件触发父组件数据修改
}
});
}
}
}
@ComponentV2
struct Child {
@Param title: string = '';
@Param fontColor: Color = Color.Black;
// 装饰回调方法,子组件触发父组件修改
@Event changeTitle: (title: string) => void = () => {};
build() {
Column() {
Text(this.title).fontColor(this.fontColor);
Button("修改标题为「新标题」").onClick(() => {
this.changeTitle("新标题"); // 触发父组件回调,修改父组件数据
});
}
}
}
❻ @Provider/@Consumer:跨组件层级双向同步
用于跨多层组件的数据同步,无需逐层传递参数,比 V1 版本更灵活、性能更优。
核心升级(对比 V1)
| 特性 | V1 @Provide/@Consume | V2 @Provider/@Consumer |
|---|---|---|
| 本地初始化 | 禁止 | 允许(找不到 Provider 时用默认值) |
| 支持类型 | 不支持 function | 支持 function |
| 深度观测 | 需配合 @Observed/@ObjectLink | 需配合 @Trace |
| 重名支持 | 默认禁止(需配置 allowOverride) | 默认支持(向上查找最近的 Provider) |
实战代码(简化版)
@Entry
@ComponentV2
struct Grandparent {
// 提供跨组件共享数据
@Provider("userInfo") user: { name: string } = { name: "Tom" };
build() {
Column() {
Parent(); // 中间组件无需传递参数
}
}
}
@ComponentV2
struct Parent {
build() {
Column() {
Child(); // 直接跨层级获取数据
}
}
}
@ComponentV2
struct Child {
// 消费跨层级共享数据
@Consumer("userInfo") user: { name: string } = { name: "" };
build() {
Text(`跨层级获取用户名:${this.user.name}`).fontSize(24);
}
}
❼ @Monitor:状态变量修改监听(替代 V1 @Watch)
用于监听状态变量的变化,支持多变量监听、获取变化前后的值,功能比 V1 @Watch更强大。
@Monitor vs @Watch 核心对比
| 特性 | @Watch | @Monitor |
|---|---|---|
| 监听数量 | 仅支持单个变量 | 支持多个变量同时监听 |
| 变化前值获取 | 不支持 | 支持(通过 IMonitor 对象) |
| 观测深度 | 仅一层属性 | 支持深度观测(配合 @Trace) |
| 使用范围 | 仅 @Component 组件 | @ComponentV2 组件 + @ObservedV2 类 |
实战代码
import log from '@open/log';
@Entry
@ComponentV2
struct Index {
@Local message: string = "Hello";
@Local age: number = 24;
// 监听单个变量
@Monitor("age")
onAgeChange(monitor: IMonitor) {
log.info(`年龄从 ${monitor.value("age")?.before} 变为 ${monitor.value("age")?.now}`);
}
// 监听多个变量
@Monitor("message", "age")
onMultiChange(monitor: IMonitor) {
monitor.dirty.forEach((path: string) => {
console.log(`${path} 变化:${monitor.value(path)?.before} → ${monitor.value(path)?.now}`);
});
}
build() {
Column() {
Button("修改年龄").onClick(() => this.age++);
Button("修改消息").onClick(() => this.message += " World");
}
}
}
❽ @Computed:计算属性(性能优化)
装饰 getter 方法,用于基于状态变量的计算逻辑,会缓存计算结果,仅当依赖的状态变化时才重新计算,提升性能。
实战代码
import log from '@open/log';
@Entry
@ComponentV2
struct Test {
@Local num: number = 1;
// 计算属性:依赖num,仅num变化时重新计算
@Computed
get numPlusOne() {
log.info("计算属性触发(仅num变化时执行)");
return this.num + 1; // 复杂计算逻辑建议用此方式,提升性能
}
build() {
Column() {
// 多次使用计算属性,仅触发一次计算
Text(`计算结果:${this.numPlusOne}`).fontSize(24);
Text(`计算结果:${this.numPlusOne}`).fontSize(24);
Button("num++").onClick(() => this.num++);
}
}
}
三、全局状态管理:AppStorageV2 + PersistenceV2
❶ AppStorageV2:全局内存状态共享
用于全局组件的内存状态共享,支持跨页面、跨组件同步,配合@ObservedV2/@Trace实现深度监听。
实战代码
// 定义共享数据类
@ObservedV2
export class Stu {
@Trace name: string = "王五";
@Trace age: number = 10;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
// 页面A:修改全局状态
@Entry
@ComponentV2
struct PageA {
// 连接全局状态,不存在则初始化
@Local son: Stu = AppStorageV2.connect(Stu, () => new Stu("张三", 12));
build() {
Column() {
Text(`${this.son.name} 今年 ${this.son.age} 岁`).fontSize(24);
Button("年龄+1").onClick(() => this.son.age++);
Button("跳转PageB").onClick(() => router.pushUrl({ url: "pages/PageB" }));
}
}
}
// 页面B:获取全局状态(同步更新)
@Entry
@ComponentV2
struct PageB {
@Local son: Stu = AppStorageV2.connect(Stu, () => new Stu("", 0));
build() {
Column() {
// 实时同步PageA修改的全局状态
Text(`${this.son.name} 今年 ${this.son.age} 岁`).fontSize(24);
Button("返回").onClick(() => router.back());
}
}
}
❷ PersistenceV2:全局持久化存储(替代 V1 PersistentStorage)
用于全局状态的持久化存储(存储到磁盘),支持类类型存储,比 V1 更强大。
实战代码
// 定义持久化数据类(复杂类型需用@Type标注)
import { Type } from '@kit.ArkUI';
@ObservedV2
class SampleChild {
@Trace p1: number = 0;
p2: number = 10;
}
@ObservedV2
export class Sample {
@Type(SampleChild) // 复杂类型需标注@Type,确保序列化成功
@Trace f: SampleChild = new SampleChild();
}
// 页面:持久化存储数据
@Entry
@ComponentV2
struct PageA {
// 连接持久化存储,不存在则初始化
prop: Sample = PersistenceV2.connect(Sample, () => new Sample())!;
build() {
Column() {
Text(`持久化数据:${this.prop.f.p1}`).fontSize(30);
Button("p1+1").onClick(() => {
this.prop.f.p1++; // 自动持久化到磁盘,应用重启后数据不丢失
});
}
}
}
四、核心总结:V2 状态管理最佳实践
- 组件内部状态 → 用@Local(配合@ObservedV2/@Trace实现深度观测);
- 组件外部输入 → 用@Param(必填参数 +@Require);
- 子组件修改父组件数据 → 用@Event回调;
- 跨层级组件共享 → 用@Provider/@Consumer;
- 状态变化监听 → 用@Monitor(多变量 + 变化前后值);
- 计算逻辑 → 用@Computed(性能优化);
- 全局内存共享 → 用AppStorageV2;
- 全局持久化 → 用PersistenceV2。
通过以上组合,可以覆盖鸿蒙开发中所有状态管理场景,既保证语义清晰,又提升开发效率和性能。
五、状态管理企业级面试题
状态管理有哪些
组件状态 @State、@Prop、@Link、@Provide/@Consum、@Observed/@ObjectLink
应用状态 LocalStorage、AppStorage、PersistentStorage、Environment
以及状态管理周边的@Watch、$$运算符、@Track
还有v2新增的状态管理:@ObservedV2/@Trace、@Local、@Param、@Once、@Require、@Event、@Provider/@Consumer、@Monitor、@Computed
面试官提问方式
- 状态管理,修饰器有哪些
- 大概谈一谈你的那些状态管理,都用到了那些
- 组件拥有的状态管理
- 鸿蒙中的全局状态管理有哪些方式
AppStorage和Localstorage区别、哪里用?
区别
- 场景:LocalStorage页面状态、AppStorage全局状态
- 功能:AppStorage配合PersistentStorage支持数据持久化
场景:在多个页面之间进行状态数据共享有哪些方法
v2新增了哪些装饰器,为什么升级,有什么用
@ObservedV2/@Trace 属性深度侦听 可以解决对象深层数据响应式失效问题、还有@ObjectLink父级UI不刷新问题
@Local 纯组件内部状态、更加语义化 @State也可以接收父组件传递的数据
@Param 父传子 v1太多了
@Once 初始化同步一次
@Event 子调用父的方法改变数据 子改父
@Provider/@Consumer 跨组件层级双向同步,更强了支持方法、支持初始化、支持别名等
@Monitor 状态变量修改侦听,更强可以一次性侦听多个,可以获取修改前后数据
@Computed 计算属性
✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃
✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃
✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃
^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤