HarmonyOS 状态管理 V2 完全指南:从 @Local 到 @ObservedV2
引言数据是声明式 UI 开发的核心一切 UI 的变化都是由数据驱动的。其中会随着用户交互、时间推移或外部事件而动态变化且会影响界面效果的数据我们称之为状态。对这些状态进行统一的定义、追踪、更新和共享这一系列实践就是状态管理。使用状态管理可以让变化有迹可循、数据共享更轻松、代码结构更清晰、应用运行更流畅。本节课将系统讲解 HarmonyOS 状态管理 V2 的核心概念与基础用法。核心内容状态管理基础概念状态定义会随着用户交互、时间推移或外部事件而动态变化且会影响界面效果的数据称之为状态。常见示例包括滑动开关控制按钮的点亮/关闭状态、计数器数值随点击增加等。状态是声明式 UI 开发的核心一切 UI 变化都由数据驱动。状态与 UI 的关系状态State驱动 UI 更新的数据用户触发组件事件修改状态数据后会触发 UI 重新渲染。UI视图将状态变量的修改映射渲染到界面。交互逻辑用户在 UI 触发事件 → 修改状态 → 状态变更触发 UI 重新渲染形成双向联动。只有被装饰器修饰的变量才是状态变量状态变量改变才会触发 UI 渲染刷新未使用装饰器的变量仅会在初始化时渲染后续变更不会刷新 UI。定义组件内状态Local使用要求在ComponentV2修饰的自定义组件中使用Local装饰器定义组件内部状态变量。核心规则只有被Local修饰的变量发生变更时才会触发 UI 更新未使用Local修饰的 public 变量变更不会触发 UI 刷新。ComponentV2 struct Parent { Local value1: number 0; // Local 修饰变更会触发 UI 更新 public value2: number 0; // 普通 public 变量变更不会触发 UI 更新 build() { Column() { Text(this.value1.toString()) Text(this.value2.toString()) Row() { Button(-1).onClick(() { this.value1 - 1; // UI 更新 this.value2 - 1; // UI 无变化 }) Button(1).onClick(() { this.value1 1; // UI 更新 this.value2 1; // UI 无变化 }) } } } }参数输入子组件接收外部数据Param装饰器选择使用Param装饰器让子组件接收父组件传入的外部数据实现组件复用。单向数据流规则Param修饰的变量遵循单向数据绑定原则数据只能从父组件流向子组件子组件不能直接修改Param接收的数据直接修改编辑器会报只读属性错误。使用场景将通用 UI 模块封装为可复用组件通过传入不同参数展示不同内容。// 父组件传入参数 struct Parent { Local value1: number 0; Local value2: number 0; build() { Column() { MyCounter({ title: value1, value: this.value1 }) MyCounter({ title: value2, value: this.value2 }) } } } // 子组件使用 Param 接收参数 struct MyCounter { Param title: string ; Param value: number 0; build() { Row() { Text(this.title) Text(this.value.toString()) } } }父子组件事件通信Event解决问题子组件需要修改父组件传入的状态数据时通过Event装饰器实现子到父的事件通信。实现逻辑子组件通过Event定义事件方法父组件将对应方法实现作为参数传入子组件子组件触发事件时即可执行父组件的方法逻辑完成状态修改。// 父组件传入修改状态的方法实现 struct Parent { Local index: number 0; build() { Column() { Text(Parent) Child({ index: this.index, changeIndex: (val: number) { this.index val; } }) } } } // 子组件Event 定义方法触发父组件传入的实现 ComponentV2 struct Child { Param index: number 0; Event changeIndex: (val: number) void; build() { Column() { Text(Child) Text(this.index.toString()) Button(Change to 20) .onClick(() { this.changeIndex(20); }) } } }跨组件状态共享Provider Consumer解决问题跨多层级组件共享状态时Param和Event需要逐级透传参数增加组件负担。使用ProviderConsumer可以实现跨层级状态与事件共享无需逐级传递。核心规则Provider和Consumer需要通过相同的状态名称和类型建立同步关系名称和类型必须完全一致才能完成依赖注入。使用方法父组件使用Provider定义需要共享的状态/事件作为所有后代组件的依赖提供者。任意层级的后代组件使用Consumer接收共享的状态/事件。无论后代组件层级多深都可以直接获取父组件提供的依赖不需要中间组件透传。计算属性优化状态Computed适用场景当一个状态的值由其他状态计算得到时可以使用计算属性实现自动更新简化代码逻辑。核心优势计算属性自带缓存机制只有依赖的状态发生变化时才会重新计算多次使用会直接复用缓存结果避免重复计算提升性能而普通方法每次调用都会重新执行没有缓存。使用建议计算属性适合基于已有状态生成新数据且需要多次复用计算结果的场景。普通方法适合每次调用都需要重新执行逻辑、触发副作用的场景。监听状态变化Monitor适用场景需要在状态变化时执行特定操作如触发弹窗、启动动画、获取变更前后值打日志时使用Monitor装饰器监听状态变化。使用方法监听单个状态直接通过IMonitor实例的value属性获取变更前的before值和变更后的now值。监听多个状态通过IMonitor实例遍历所有发生变更的状态通过path区分不同状态分别获取变更前后的值。对象状态管理ObservedV2 Trace基础限制常规装饰器只能观测到对象整体的赋值变更无法观测到对象内部属性的修改直接修改对象属性不会触发 UI 刷新。解决方案使用ObservedV2装饰类配合Trace装饰类的属性即可实现对象属性变更的深度观测任意层级的属性修改都会触发 UI 刷新。嵌套对象支持支持深度嵌套的类结构嵌套类同样使用ObservedV2装饰内部属性使用Trace装饰即可实现深度监听修改嵌套属性也能正常触发刷新。ObservedV2 class Product { Trace id: number 0; Trace name: string ; Trace price: number 0; Trace quantity: number 0; Trace checked: boolean false; } ComponentV2 struct ShoppingCart { Local products: Product[] []; build() { ForEach(this.products, (item: Product) { ProductItem({ product: item }) }) } }可以配合Monitor实现对复杂对象属性变更的监听获取属性变更前后的值。综合案例购物车应用状态管理功能梳理支持修改商品数量、勾选结算、全选反选、删除勾选商品、计算总金额和总数量。状态管理设计集中管理所有核心状态商品列表、总金额、总数量定义在根组件ShoppingCart中统一管理。参数传递父子组件传参使用Param接收商品列表数据跨组件调用修改方法使用ProviderConsumer避免透传。计算属性总数量、总金额依赖商品列表数据计算使用计算属性实现依赖变更自动重新计算。深度观测商品类使用ObservedV2Trace实现对象属性变更观测直接修改商品属性即可触发 UI 刷新。总结本节课系统讲解了 HarmonyOS 状态管理 V2 的核心装饰器与使用场景装饰器/特性核心作用关键规则Local定义组件内部状态变量只有Local修饰的变量变更才触发 UI 更新Param子组件接收父组件传入数据单向数据流子组件不可直接修改否则报错Event子组件向父组件传递事件子组件通过Event定义父组件传入方法实现ProviderConsumer跨层级状态/事件共享名称和类型必须完全一致才能建立同步关系Computed基于其他状态计算派生值自带缓存依赖变化才重新计算Monitor监听状态变化可获取变更前后的值支持单/多状态监听ObservedV2Trace复杂对象深度观测装饰类 装饰属性支持嵌套对象深度监听