
状态管理 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 对比用法StateLocal参数无无初始化可从外部初始化不允许外部初始化观察能力能观察变量本身以及一层的成员属性无法深度观测能观测变量本身深度观测依赖 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:stringassassin;Traceage:number20;constructor(name:string,age:number){this.namename;this.ageage;}}// 父组件EntryComponentV2exportstruct Comp{Localinfo:InfonewInfo(assassin,20);build(){Column({space:20}){Text(parent info: this.info.name, age: this.info.age)// 传递对象实例到子组件ChildComp({param:this.info})}}}ComponentV2exportstruct ChildComp{// param 虽然在本地初始化了但是父组件传递了值其会覆盖本地初始值Paramparam:InfonewInfo(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 将子组件实际的变化能力交由父组件处理在父组件实际决定如何处理后将最终值在渲染前同步回子组件EntryComponentV2exportstruct Comp{Localcount:number0;build(){Column({space:20}){Text(parent count: this.count)ChildComp({count:this.count,changeFactory:(){this.count;}})}}}ComponentV2exportstruct ChildComp{Paramcount:number10;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变化会同步给对应的 ConsumerConsumer 装饰器Consumer 装饰器说明装饰器参数aliasName别名向上查找 Provider 匹配的 key缺省时默认为属性名支持类型自定义组件中成员变量number、string、boolean、class、Array、Date、Map、Set 等类型支持箭头函数初始化必须本地初始化禁止从父组件初始化观察能力能力等同于 Trace变化会同步给对应的 ProvideraliasName 是用于 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/ConsumeConsume(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:stringassassin;Traceage:number20;constructor(name:string,age:number){this.namename;this.ageage;}}EntryComponentV2exportstruct Comp{Provider(user)info:InfonewInfo(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:InfonewInfo(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 装饰箭头函数EntryComponentV2exportstruct Comp{LocalchildX:number0;LocalchildY:number0;Provider()//aliasName 缺省使用变量名作为 aliasNameonDrag:(x:number,y:number)void(x:number,y:number){console.log(onDrag event x:${x}, y:${y});this.childXx;this.childYy;}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 和 ConsumerBuilderNode 内部定义的 Consumer 必须设置一个合法的默认值应避免 Consumer 装饰对象实例否则会导致重复创建、独立实例而无法同步BuilderNode 节点需通过 NodeController 添加到组件树后其内部的 Consumer 才会尝试向上匹配最近的 Provide建立双向同步关系若匹配不到则 Consumer 使用默认值调用removeChild移除子节点后子节点从组件树卸载子组件内的 Consumer 会再次视图查找对应的 Provider若组件树卸载后无法找到匹配的 Provider则断开和 Provider 的双向同步关系Consumer 装饰的变量恢复成默认值调用dispose释放 BuilderNode 节点该节点销毁会触发子组件的 aboutToDisappear 回调import{BuilderNode,FrameNode,NodeController,UIContext}fromkit.ArkUI;ComponentV2struct TestNode{Consumer()content:stringdefault 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.contentcon_})}}}BuilderfunctionbuildText(){TestNode()}letglobalBuilderNode:BuilderNode[]|nullnull;classTextNodeControllerextendsNodeController{privaterootNode:FrameNode|nullnull;privateuiContext:UIContext|nullnull;constructor(){super();}makeNode(context:UIContext):FrameNode|null{this.rootNodenewFrameNode(context);this.uiContextcontext;returnthis.rootNode;}addBuildNode():void{if(globalBuilderNodenullthis.uiContext){globalBuilderNodenewBuilderNode(this.uiContext);// 构建 BuilderNodeTestNode 作为子组件// enableProvideConsumeCrossing 设为 true支持 Provider/Consumer 跨组件同步globalBuilderNode.build(wrapBuilder[](buildText),undefined,{enableProvideConsumeCrossing:true});}if(this.rootNodeglobalBuilderNode){// 添加子组件this.rootNode.appendChild(globalBuilderNode.getFrameNode());}}// 移除子节点 (TestNode 组件)removeBuilderNode():void{if(this.rootNodeglobalBuilderNode){this.rootNode.removeChild(globalBuilderNode.getFrameNode());}}// 释放 BuilderNode 子节点 (TestNode)随后该节点销毁触发子节点的 aboutToDisappear 回调disposeNode():void{if(this.rootNodeglobalBuilderNode){globalBuilderNode.dispose();}}}EntryComponentV2exportstruct ProviderComp{Provider()content:stringcontent;// 监听 content 的变化Monitor(content)providerWatch(){console.info(provider change${this.content})}nodeController:TextNodeControllernewTextNodeController();build(){Column({space:20}){Text(Provider:${this.content})// 添加 BuilderNode, Consumer 与 Consumer 建立双向同步Button(add child node).onClick((){this.nodeController.addBuildNode();})// 移除 BuilderNodeConsumer 与 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.contentPro_;})// 子节点NodeContainer(this.nodeController)}.width(100%)}}