ArkUI -- 状态管理 V2
2026/6/10 4:05:22 网站建设 项目流程

状态管理 V1 使用代理观察数据,创建状态变量时,会同时创建一个代理观察者,该观察者可以感知代理变化,但无法精准观测到实际数据变化。V1 侧重于组件层级的状态管理,V1 若要实现深度观察能力,需通过 @ObjectLink 逐层拆解嵌套类,并配合自定义组件使用。

状态管理 V2 则增强了对数据对象的深度观察与管理能力,使数据本身可观察,更新数据时,会触发相应视图的更新,UI刷新更加高效,可灵活地控制数据与状态,提升应用性能。

特性状态管理 V1状态管理 V2
独立于 UI状态变量不能独立于 UI状态变量独立于 UI,更新数据会触发相应视图的更新
深度观测无法深度观测和深度监听,只能感知对象属性第一层的变化支持对象的深度观测和深度监听,且不影响观测性能
精准更新更新对象中属性时,存在冗余更新的问题支持对象中属性级精准更新
易用性装饰器间配合使用限制多,不易用,不利于组件化装饰器易用性高、拓展性强,有利于组件化

@Local:组件内部状态

状态管理 V1 @State 装饰的变量,能够从外部初始化,即无法确保 @State 装饰变量的初始值一定为组件内部定义的值,这不利于自定义组件内部状态的管理

使用 @Local 装饰 @ComponentV2 自定义组件中的变量,可使变量具有观察变化的能力

  • @Local 装饰器只能在 @ComponentV2 装饰的自定义组件中使用
  • @Local 装饰的变量表示组件内部的状态,不允许从外部传入初始化
@Local 装饰器说明
装饰器参数
可装饰的变量类型Object、class、string、number、boolean、enum 等基本类型以及 Array、Date、Map、Set 等内置类型
支持 null、undefined 以及联合类型
装饰变量的初始值必须本地初始化,不允许外部传入初始化
装饰变量的传递@Local 装饰的变量可传递给子组件 @Param 装饰的变量,且可同步变化
观察能力仅限于被装饰的变量本身 (装饰 @ObservedV2 对象能深度观察,装饰普通对象这能观察其整体赋值)

观察变化

  • 当装饰的变量为 boolean、string、number 时,可观察到对变量赋值的变化

  • 当装饰的变量为对象时

    • 普通对象,仅可观察到对类对象整体赋值的变化,无法直接观察到类成员属性的变化
    • @ObservedV2@Trace装饰的类和属性,或makeObserved转换的可观察对象,可观察到类成员属性的变化

    API19 开始,支持 @Local 和状态管理 V1 的 @Observed 装饰器同时使用,需遵守混用规则

  • 当装饰简单类型数组时,可观察到数组整体或数组项的变化

  • 当装饰的变量为嵌套类或对象数组时,@Local 对深层对象属性的观察,依赖于 @ObservedV2 与 @Trace 装饰器

  • 当装饰 Array、Date、Map、Set 时,可观察到变量整体赋值以及 API 调用带来的变化

@Local 与 @State 对比

用法@State@Local
参数
初始化可从外部初始化不允许外部初始化
观察能力能观察变量本身以及一层的成员属性,无法深度观测能观测变量本身,深度观测依赖 @ObservedV2 与 @Trace 装饰器
数据传递可作为数据源和子组件中状态变量同步可作为数据源和子组件中@Param装饰的状态变量同步

@Param: 组件外部输入

状态管理 V1 存在多种可接受外部传入的装饰器,常用的有 @State、@Prop、@Link、@ObjectLink,这些装饰器使用有限制且不易区分,不当使用会导致性能问题

  • 对于复杂类型 (如:对象),@Param 会接受数据源的引用;在组件内可修改类对象中的属性,且修改会同步回数据源
@Param 装饰器说明
装饰器参数
可装饰的变量类型Object、class、string、number、boolean、enum 等基本类型以及 Array、Date、Map、Set 等内置类型
支持 null、undefined 以及联合类型
能否本地修改不可,若需修改值,需搭配@Once修改子组件的本地值,或通过@Event修改 @Param 数据源的值
同步类型由父到子组件的单向同步 (但是对于对象的属性值的修改,会同步回父组件)
装饰变量的初始值允许本地初始化,若不在本地初始化则需与@Require一起使用,则必须从外部传入初始值
观察能力仅限于被装饰的变量本身 (装饰 @ObservedV2 对象能深度观察,装饰普通对象这能观察其整体赋值)

@Param 装饰的变量,不可本地修改 (整体赋值),单向同步
但是对于复杂类型 (如:对象),@Param 接收数据源的引用,可本地修改属性值,且在子组件修改对象中的属性值,会同步回父组件,且会触发父组件与子组件 UI 的刷新

  • @Param 装饰器只能在 @ComponentV2 装饰的自定义组件中使用
  • @Param 支持本地和外部传入值的初始化,当存在外部传入值时,优先使用外部传入的值初始化
  • @Param 装饰的变量在子组件中无法被直接修改,但,若装饰的变量是对象类型,在子组件中可以修改对象的属性

【示例】

@ObservedV2classInfo{@Tracename:string='assassin';@Traceage:number=20;constructor(name:string,age:number){this.name=name;this.age=age;}}// 父组件@Entry@ComponentV2exportstruct Comp{@Localinfo:Info=newInfo('assassin',20);build(){Column({space:20}){Text('parent info: '+this.info.name+', age: '+this.info.age)// 传递对象实例到子组件ChildComp({param:this.info})}}}@ComponentV2exportstruct ChildComp{// param 虽然在本地初始化了,但是父组件传递了值,其会覆盖本地初始值@Paramparam:Info=newInfo('child',10);build(){Column({space:20}){Text('child: '+this.param.name+', age: '+this.param.age)Button('change age').onClick(()=>{// 在子组件修改 @Param 装饰变量的属性值,会同步回父组件,即父、子组件会同步刷新 UIthis.param.age++;})}}}

@Once: 初始化同步一次

若要实现仅从外部初始化一次且不接受后续同步变化的能力,需搭配使用 @Once 和 @Param 装饰器。

@Once 装饰的变量在初始化时接受外部传入值进行初始化,后续数据源更改不会同步给子组件

  • @Once 不影响 @Param 的观测能力,仅针对数据源的变化做拦截
  • @Once 必须搭配 @Param 使用,且不可搭配其它装饰器,搭配使用后,可在本地修改 @Param 变量的值

@Once 和 @Param 搭配装饰的状态变量为对象时

  • 若只修改属性值,属性值的改变是双向同步的
  • 若父组件 new 了新的实例,则不会同步给子组件
  • 若子组件 new 了新的实例,则后续对象属性值的改变,也不会同步回父组件

@Event: 规范组件输出

@Event 用于装饰组件对外输出的方式,主要用于配合 @Param 实现数据的双向同步。

由于 @Param 装饰的变量在本地无法更改,使用 @Event 装饰器装饰回调方法并调用,可实现子组件向父组件要求更新数据源的变量,再通过 @Local 的同步机制,将修改同步回 @Param 装饰的变量,以达到主动更新 @Param 装饰变量的效果。

@Param 标志着组件的输入,表明该变量受父组件影响,而 @Event 标志着组件的输出,可以通过该方法影响父组件。

@Event 只能在 @ComponentV2 装饰的自定义组件中使用,若装饰非方法类型的变量,不会有任何作用。

@Event 装饰器说明
装饰器参数
可装饰的变量类型回调方法,如:()=>void(x: number)=>boolean
可传入的函数类型箭头函数
初始化优先用外部传入的值,否则使用本地默认值,若没有初始化,会自动生成一个空的函数作为默认的回调

需要注意的是,使用 @Event 修改父组件的值是立刻生效的,但父组件将变化同步回子组件的过程是异步的,即在调用完 @Event 的方法后,子组件内的值不会立刻变化。
这是因为 @Event 将子组件实际的变化能力交由父组件处理,在父组件实际决定如何处理后,将最终值在渲染前同步回子组件

@Entry@ComponentV2exportstruct Comp{@Localcount:number=0;build(){Column({space:20}){Text('parent count: '+this.count)ChildComp({count:this.count,changeFactory:()=>{this.count++;}})}}}@ComponentV2exportstruct ChildComp{@Paramcount:number=10;@EventchangeFactory:()=>void;build(){Column({space:20}){Text('child: '+this.count)Button('child change').onClick(()=>{// param 被 @Param 装饰,不可在本地修改,需通过 @Event 装饰的回调函数,通知父组件// this.count ++; //编译报错this.changeFactory();})}}}
  • @ComponentV2 内的回调函数,是私有的,不接受外部传入,若不用 @Event,则需使用 @Param 装饰,父组件才可传值
  • @Param 装饰的回调函数,则失去了 @Event 提供的类型约束、默认空函数兜底、编译校验

@Provider 和 @Consumer: 跨组件层级双向同步

@Provider 和 @Consumer 用于跨组件层级数据双向同步,只能在 @ComponentV2 中使用。

  • @Provider,即数据提供方,其所有的子组件都可以通过 @Consumer 绑定相同的 key 来获取 @Provider 提供的数据
  • @Consumer,即数据消费方,可通过绑定同样的 key 获取其最近父节点的 @Provider 的数据 (@Provider 可以重名),必须本地初始化,若查找不到对应的 @Provider,则使用本地默认值

@Provider 装饰器

@Provider 装饰器说明
装饰器参数aliasName,别名,作为与 @Comsumer 匹配的 key,缺省时默认为属性名
支持类型自定义组件中成员变量,number、string、boolean、class、Array、Date、Map、Set 等类型,支持箭头函数
初始化必须本地初始化,禁止从父组件初始化
观察能力能力等同于 @Trace,变化会同步给对应的 @Consumer

@Consumer 装饰器

@Consumer 装饰器说明
装饰器参数aliasName,别名,向上查找 @Provider 匹配的 key,缺省时默认为属性名
支持类型自定义组件中成员变量,number、string、boolean、class、Array、Date、Map、Set 等类型,支持箭头函数
初始化必须本地初始化,禁止从父组件初始化
观察能力能力等同于 @Trace,变化会同步给对应的 @Provider

aliasName 是用于 @Provider 和 @Consumer 进行匹配的唯一指定 key,缺省时默认为属性名

  • @Provider 和 @Consumenr 装饰的数据类型需一致
  • @Provider 和 @Consumenr 强依赖自定义组件层级,@Provider 可以重名,@Consumer 以最近父节点的 @Provider 的数据初始化,即 @Consumer 会因为所在组件的父组件不同,而被初始化为不同的值
  • @Provider 和 @Consumenr 相当于把组件粘合在一起了,从组件独立的角度,应减少使用
  • @Provider 和 @Consumenr 只支持本地初始化,禁止从父组件初始化,但可用于初始化子组件中 @Param 装饰的变量

V2 的 @Provider/@Consumer 和 V1 的 @Provide/@Consume 对比

能力V2 装饰器 @Provider/@ConsumerV1 装饰器 @Provide/@Consume
@Consume(r)必须本地初始化,当找不到 @Provider 时使用本地默认值API20 之前,禁止本地初始化;API 20 开始,支持设置默认值,若没有设置默认值,且找不到对应的 @Provide 时,会抛出异常
@Provide(r)不允许从父组件初始化允许从父组件初始化
默认开启重载,即@Provider 可以重名,@Consumer 向上查找最近的 @Provider默认关闭,即不允许有同名的 @Provide,若需重载,需搭配allowOverride
匹配的 keyalias是唯一匹配的 key,缺省时默认属性名为 aliasalias和属性名都可为 key,优先匹配 alias,匹配不到则匹配属性名
观察能力仅能观察自身赋值变化,若需观察嵌套场景,需配合@Trace使用观察第一层变化,若需观察嵌套场景,需配合@Observed@ObjectLink一起使用
是否支持 function支持不支持
@Provider/@Consumer 装饰复杂类型

@Provider 和 @Consumer 只能观察到数据本身的变化,若需观察复杂数据类型的属性变化,可配置 @Trace 一起使用,或通过 makeObserved 将非可观察数据变为可观察数据

@ObservedV2classInfo{@Tracename:string='assassin';@Traceage:number=20;constructor(name:string,age:number){this.name=name;this.age=age;}}@Entry@ComponentV2exportstruct Comp{@Provider('user')info:Info=newInfo('Assassin',20)build(){Column({space:20}){Text('parent: '+this.info.name+'_'+this.info.age)Button('parent change').onClick(()=>{// Info 被 @ObservedV2 装饰,且 age 属性被 @Trace 装饰,其变化可触发 UI 刷新this.info.age++;})// 子组件ChildComp()}}}@ComponentV2exportstruct ChildComp{// 父组件有 'user',使用 @Provider 的值初始化@Consumer('user')info:Info=newInfo('child',10);build(){Column({space:20}){Text('child: '+this.info.name+'_'+this.info.age)Button('child change').onClick(()=>{// @Provider 和 @Consumenr 双向同步this.info.age++;})}}}
@Provider/@Consumer 装饰箭头函数
@Entry@ComponentV2exportstruct Comp{@LocalchildX:number=0;@LocalchildY:number=0;@Provider()//aliasName 缺省,使用变量名作为 aliasNameonDrag:(x:number,y:number)=>void=(x:number,y:number)=>{console.log(`onDrag event x:${x}, y:${y}`);this.childX=x;this.childY=y;}build(){Column({space:20}){Text(`child position, x:${this.childX}, y:${this.childY}`)ChildComp()}}}@ComponentV2exportstruct ChildComp{// 子组件通过调用回调函数,将拖拽的坐标信息同步回父组件@Consumer()onDrag:(x:number,y:number)=>void=(x:number,y:number)=>{}build(){Button('drag').draggable(true).onDragStart((event:DragEvent)=>{// 当前预览器上不支通用拖拽事件this.onDrag(event.getDisplayX(),event.getGlobalDisplayX());})}}
跨 BuilderNode 下 @Provider 和 @Consumer 双向同步

从 API 23 开始,支持跨 BuilderNode 配对 @Provider 和 @Consumer

  • 默认情况下,@Provider/@Consumer 可能无法跨越 BuilderNode 边界进行同步,需在创建 BuilderNode 时,通过配置 BuildOptions 中的enableProvideConsumeCrossing: true,以允许状态同步跨越 BuilderNode 边界,支持跨 BuilderNode 配对 @Provider 和 @Consumer
  • BuilderNode 内部定义的 @Consumer 必须设置一个合法的默认值,应避免 @Consumer 装饰对象实例,否则会导致重复创建、独立实例而无法同步
  • BuilderNode 节点需通过 NodeController 添加到组件树后,其内部的 @Consumer 才会尝试向上匹配最近的 @Provide,建立双向同步关系;若匹配不到,则 @Consumer 使用默认值
  • 调用removeChild移除子节点后,子节点从组件树卸载,子组件内的 @Consumer 会再次视图查找对应的 @Provider,若组件树卸载后无法找到匹配的 @Provider,则断开和 @Provider 的双向同步关系,@Consumer 装饰的变量恢复成默认值
  • 调用dispose释放 BuilderNode 节点,该节点销毁,会触发子组件的 aboutToDisappear 回调
import{BuilderNode,FrameNode,NodeController,UIContext}from"@kit.ArkUI";@ComponentV2struct TestNode{@Consumer()content:string='default value';// 设置字符串默认值,而非对象实例// 监听 content 的变化@Monitor('content')consumerWatch(){console.info(`consumer change${this.content}`)}// 节点卸载后,生命周期回调aboutToDisappear():void{console.log('TestNode aboutToDisappear');}build(){Column({space:20}){Text('Consumer: '+this.content)Button('Consumer change').onClick(()=>{this.content+='con_'})}}}@BuilderfunctionbuildText(){TestNode()}letglobalBuilderNode:BuilderNode<[]>|null=null;classTextNodeControllerextendsNodeController{privaterootNode:FrameNode|null=null;privateuiContext:UIContext|null=null;constructor(){super();}makeNode(context:UIContext):FrameNode|null{this.rootNode=newFrameNode(context);this.uiContext=context;returnthis.rootNode;}addBuildNode():void{if(globalBuilderNode==null&&this.uiContext){globalBuilderNode=newBuilderNode(this.uiContext);// 构建 BuilderNode,TestNode 作为子组件// enableProvideConsumeCrossing 设为 true,支持 @Provider/@Consumer 跨组件同步globalBuilderNode.build(wrapBuilder<[]>(buildText),undefined,{enableProvideConsumeCrossing:true});}if(this.rootNode&&globalBuilderNode){// 添加子组件this.rootNode.appendChild(globalBuilderNode.getFrameNode());}}// 移除子节点 (TestNode 组件)removeBuilderNode():void{if(this.rootNode&&globalBuilderNode){this.rootNode.removeChild(globalBuilderNode.getFrameNode());}}// 释放 BuilderNode 子节点 (TestNode),随后该节点销毁,触发子节点的 aboutToDisappear 回调disposeNode():void{if(this.rootNode&&globalBuilderNode){globalBuilderNode.dispose();}}}@Entry@ComponentV2exportstruct ProviderComp{@Provider()content:string='content';// 监听 content 的变化@Monitor('content')providerWatch(){console.info(`provider change${this.content}`)}nodeController:TextNodeController=newTextNodeController();build(){Column({space:20}){Text(`Provider:${this.content}`)// 添加 BuilderNode, @Consumer 与 @Consumer 建立双向同步Button('add child node').onClick(()=>{this.nodeController.addBuildNode();})// 移除 BuilderNode,@Consumer 与 @Provider 断开连接,恢复默认值Button('remove child node').onClick(()=>{this.nodeController.removeBuilderNode();})// 释放 BuilderNode 子节点 (TestNode),随后该节点销毁,触发子节点的 aboutToDisappear 回调Button('dispose child node').onClick(()=>{this.nodeController.disposeNode();})// @Provider/@Consumer双向同步更新Button('change Provider').onClick(()=>{this.content+='Pro_';})// 子节点NodeContainer(this.nodeController)}.width('100%')}}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询