鸿蒙 状态管理 (管理组件)

549 阅读17分钟

@State装饰器:组件内状态

概述

@State装饰的变量,是私有的,只能从组件内部访问,声明时要指定类型和初始化。

@State装饰的变量拥有以下的特点:

  • @State装饰的变量与子组件中的@Prop装饰的变量之间建立数据的单项同步,与@Link、@ObjectLink装饰的变量建立双向数据同步。
  • @State装饰的变量的生命周期与其所属自定组件的生命周期相同。

装饰器的使用规则说明

@State变量装饰器说明
装饰器参数
同步类型不与父组件中的任何类型的变量同步
允许装饰的类型1.Object、class、string、number、boolean、enum类型,以及这些类型的数组。2.不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。3.类型必须被指定
被装饰变量的初始值必须本地初始化

变量的传递/访问规则说明

传递/访问说明
从父组件初始化1.可选,从父组件初始化或者本地初始化。如果从父组件初始化将会覆盖本地初始化。 2.支持父组件中常规变量、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocaStorageProp装饰的变量,初始化子组件的@State
用于初始化子组件@State装饰的变量只支持初始化子组件的常规变量、@State、@Link、@Prop、@provide
是否支持组件外访问只支持组件内访问

观察变化和行为表现

并不是状态变量的所有改变都会引起UI的刷新,只有可以被框架观察到的修改才会引起UI刷新。

观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
  • 当装饰的数据类型为class或者Object时,能观察到自身的赋值变化,属性的赋值变化,但是不能观察到嵌套类型的赋值变化。
  • 当装饰的对象是array时,能观察到自身的赋值和添加、删除、更新数组的变化。数组项中属性的赋值观察不到(也是嵌套问题)

框架行为

  • 当状态变量被改变时,查询依赖该状态变量的组件
  • 执行依赖该状态变量的组件的更新方法,组件更新渲染
  • 和该状态变量不相关的组件或者UI描述,不会发生重新渲染,从而实现页面渲染的按需更新

@Prop装饰器:父子单项同步

概述

@Prop装饰的变量和父组件建立单向体同步关系:

  • @Prop变量允许在本地修改,但修改之后不会同步给父组件
  • 当父组件的数据产生变化的时候,与之相关的@Prop修饰的变量会自动更新。

限制条件

@Prop装饰器不能在@Entry装饰的自定义组件中使用。(因为@Entry声明的含义是入口组件,所以没有父组件)(个人理解)

装饰器使用规则说明

@Prop变量装饰器说明
装饰器参数
同步类型单向同步: 修改父组件的状态变量的时候,将同步给子组件中被@Prop修饰的变量,子组件被@Prop修饰的变量改变不会同步给父组件的状态变量
允许装饰的变量类型1.只支持string、number、boolean、enum类型。2.不支持array,不允许使用undefined和null。3.必须指定类型。
被装饰变量的初始值允许本地初始化 现在不支持初始化

变量的传递/访问规则说明

传递/访问说明
从父组件初始化1.如果本地有初始化,则是可选的,没有的话则必选。 2.支持父组件中常规变量、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocaStorageProp去初始化子组件中的@Prop变量
用于初始化子组件@Prop支持去初始化子组件的常规变量、@State、@Link、@Prop、@provide
是否支持组件外访问@Prop装饰的变量是私有的,只能在组件内访问

观察变化和行为表现

观察变化

@Prop装饰的数据可以观察到以下变化:

  • 当装饰的类型是允许类型,即string、number、boolean、enum类型都可以观察到赋值的变化。

对于@State和@Prop的同步场景

  • 使用父组件中的@State变量的值初始化子组件中的@Prop变量。当@State变量变化时,该变量的值也会同步更新至@prop变量
  • @Pro装饰的变量的修改不会影响其数据源@State装饰变量的值
  • 除了@State,数据源也可以用@Link或者@Prop装饰,对@Prop的同步机制是相同的
  • 数据源和@Prop变量的类型需要相同

框架行为

要理解@Prop变量值初始化和更新机制,有必要了解父组件和拥有@Prop变量的子组件初始渲染和更新流程

  1. 初始渲染:

    1.执行父组件的build()函数将创建子组件的新实例,将数据源传递给子组件。 2.初始化子组件@Prop装饰的变量。

  2. 更新:

    1.子组件@Prop更新时,更新仅停留在当前子组件,不会同步回父组件。 2.当父组件的数据源更新时,子组件的@prop装饰的变量将被来自父组件的数据源重置,所有@Prop装饰的本地的修改将被父组件的更新覆盖。

@Link装饰器:父子双向同步

概述

@Link装饰的变量与其父组件的数据源共享相同的值。

限制条件

@Link装饰器不能在@Entry装饰的自定义组件中使用。

装饰器使用规则说明

@Link变量装饰器说明
装饰器参数
同步类型1.双向同步。2.父组件中@State,@StorageLink和@Link和子组件@Link可以建立双向数据同步,反之亦然
允许装饰的变量类型1.Object,class,string,number,boolean,enum类型,以及这些类型的数组。2.API11以及以上支持Map,Set类型。3.API及以上支持上述支持类型的联合类型。4.当使用undefined和null的时候,建议显示指定类型,遵循TypeScript类型校验。
支持AR看UI框架定义的联合类型Length,ResourceStr,ResourceColor类型。类型必须被指定,且和双向绑定状态变量的类型相同。不支持any
被装饰变量的初始值无,禁止本地初始化

变量的传递/访问规则说明

访问/传递说明
从父组件初始化和更新必选。与父组件@State, @StorageLink和@Link 建立双向绑定。允许父组件中@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocalStorageProp装饰变量初始化子组件@Link。从API version 9开始,@Link子组件从父组件初始化@State的语法为Comp({ aLink: this.aState })。同样Comp({aLink: $aState})也支持。
用于初始化子组件允许,可用于初始化常规变量、@State、@Link、@Prop、@Provide。
是否支持组件外访问私有,只能在所属组件内访问。

观察变化和行为表现

观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以同步观察数值的变化
  • 当装饰的数据类型为class或者Objcet时,可以观察到赋值和属性赋值的变化。
  • 当装饰的对象是数组array时,可以观察到数组添加、删除、更新数组单元的变化。
  • 当装饰的对象是日期Date时,可以观察到Date整体的赋值,同时可以通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。
  • 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可以通过调用Map的接口set,clear,delete更新Map的值。
  • 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口add,clear,delete更新Set的值。

框架行为

@Link装饰的变量和其所属的自定义组件共享生命周期。

为了了解@Link变量初始化和更新机制,有必要先了解父组件和拥有@Link变量的子组件的关系,初始渲染和双向更新的流程(以父组件为@State为例)。

  1. 初始渲染:执行父组件的build()函数后将创建子组件的新实例。初始化过程如下:

    1. 必须指定父组件中的@State变量,用于初始化子组件的@Link变量。子组件的@Link变量值与其父组件的数据源变量保持同步(双向数据同步)。
    2. 父组件的@State状态变量包装类通过构造函数传给子组件,子组件的@Link包装类拿到父组件的@State的状态变量后,将当前@Link包装类this指针注册给父组件的@State变量。
  2. @Link的数据源的更新:即父组件中状态变量更新,引起相关子组件的@Link的更新。处理步骤:

    1. 通过初始渲染的步骤可知,子组件@Link包装类把当前this指针注册给父组件。父组件@State变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(比如@Link包装类)。
    2. 通知@Link包装类更新后,子组件中所有依赖@Link状态变量的系统组件(elementId)都会被通知更新。以此实现父组件对子组件的状态数据同步。
  3. @Link的更新:当子组件中@Link更新后,处理步骤如下(以父组件为@State为例):

    1. @Link更新后,调用父组件的@State包装类的set方法,将更新后的数值同步回父组件。
    2. 子组件@Link和父组件@State分别遍历依赖的系统组件,进行对应的UI的更新。以此实现子组件@Link同步回父组件@State。

@Provide装饰器和@Consume装饰器:与后代组件双向同步

概述

@Provide/@Consume装饰的状态变量有以下特性:

  • @Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
  • 后代组件通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步,与@State/@link不同的是,前者可以在多层级的父子组件间传递
  • @Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常。

装饰器说明

@State的规则同样适用于@Provide,差异为@provide还作为多层后代的同步源。

@Provide变量装饰器说明
装饰器参数别名:常量字符串,可选。如果制定了别名,则通过别名来绑定变量;如果未指定别名,则通过变量名绑定变量
同步类型双向同步
允许装饰的变量类型Object、class、string、number、boolean、enum类型,以及这些类型的数组。支持Date类型。API11及以上支持Map、Set类型。。API11及以上支持上述支持类型的联合类型,注意当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验
支持ArkUI框架定义的联合类型Length、ResourceStr、ResourceColor类型。不支持any。必须指定类型。@Provide变量的@Consume变量的类型必须相同。
被装饰变量的初始值必须指定
支持allowOverride参数允许重写,只要声明了allowOverride,则别名和属性名都可以被Override。示例见@Provide支持allowOverride参数。
@Consume变量装饰器说明
装饰器参数别名:常量字符串,可选。如果提供了别名,则必须有@Provide的变量和其有相同的别名才可以匹配成功;否则,则需要变量名相同才能匹配成功。
同步类型双向同步
允许装饰的变量类型Object、class、string、number、boolean、enum类型,以及这些类型的数组。支持Date类型。API11及以上支持上述支持类型的联合类型。注意当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验
支持ArkUI框架定义的联合类型Length、ResourceStr、ResourceColor类型。不支持any。必须指定类型。@Provide变量和@Consume变量的类型必须相同。@Consume装饰的变量,在其父组件或者祖先组件上,必须有对应的属性和别名的@Provide装饰的变量。
被装饰变量的初始值无,禁止本地初始化。

变量的传递/访问规则说明

@Provide传递/访问说明
从父组件初始化和更新可选,允许父组件中常规变量(常规变量对@Prop赋值,只是数值的初始化,常规变量的变化不会触发UI刷新,只有状态变量才能触发UI刷新)、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocalStorageProp装饰的变量装饰变量初始化子组件@Provide。
用于初始化子组件允许,可用于初始化@State,@Link,@Prop,@Provide。
和父组件同步
和后代组件同步和@Consume双向同步
是否支持组件外访问私有,仅可以在所属组件内访问
@Consume传递/访问说明
从父组件初始化和更新禁止。通过相同的变量名和alias(别名)从@Provide初始化。
用于初始化子组件允许,可用于初始化@State、@Link、@Prop、@Provide。
和祖先组件同步和@Provide双向同步。
是否支持组件外访问私有,仅可以在所属组件内访问

观察变化和行为表现

观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
  • 当装饰的数据类型为class或者Object的时候,可以观察到赋值和属性赋值的变化(属性为Object.keys(observedObject)返回的所有属性)。
  • 当装饰的对象是array的时候,可以观察到数组的添加、删除、更新数组单元。
  • 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。
  • 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。
  • 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。

框架行为

  1. 初始渲染:

    1. @Provide装饰的变量会以map的形式,传递给当前@Provide所属组件的所有子组件;
    2. 子组件中如果使用@Consume变量,则会在map中查找是否有该变量名/alias(别名)对应的@Provide的变量,如果查找不到,框架会抛出JS ERROR;
    3. 在初始化@Consume变量时,和@State/@Link的流程类似,@Consume变量会保存在map中查找到的@Provide变量,并把自己注册给@Provide。
  2. 当@Provide装饰的数据变化时:

    1. 通过初始渲染的步骤可知,子组件@Consume已把自己注册给父组件。父组件@Provide变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(@Consume);
    2. 通知@Consume更新后,子组件所有依赖@Consume的系统组件(elementId)都会被通知更新。以此实现@Provide对@Consume状态数据同步。
  3. 当@Consume装饰的数据变化时:

    通过初始渲染的步骤可知,子组件@Consume持有@Provide的实例。在@Consume更新后调用@Provide的更新方法,将更新的数值同步回@Provide,以此实现@Consume向@Provide的同步更新。

@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化

概述

@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:

  • 被@Observed装饰的类,可以被观察到属性的变化
  • 子组件中@ObjectLink装饰器装饰的状态变量用于接受@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰
  • @Observed用于嵌套类场景中,观察对象类属性变化,要配合自定义组件使用,如果要做数据双/单向同步,需要搭配@ObjectLink或者@Prop使用

限制条件

  • 使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。
  • @ObjectLink装饰器不能在@Entry装饰的自定义组件中使用

装饰器说明

@Observed类装饰器说明
装饰器参数
类装饰器装饰class。需要放在class的定义前,使用new创建类对象
@ObjectLink说明
装饰器参数
允许装饰的变量类型必须为被@Observed装饰的class实例,必须指定类型。不支持简单类型,可以使用@Prop。支持继承Date,Array的class实例,API11以上支持继承Map,Set的class实例。API11以上支持@Observed装饰类和undef或null组成的联合类型。@ObjectLink的属性是可以改变的,但是变量的分配是不允许的,也就是说这个装饰器装饰的变量是只读的,不能被改变
被装饰变量的初始值不允许

变量的传递/访问规则说明

@ObjectLink传递/访问说明
从父组件初始化必须指定。初始化@ObjectLink装饰的变量必须同时满足以下场景:1.类型必须是@Observed装饰的class。2.初始化的数值需要是数组项,或者class的属性。3.同步源的class或者数组必须是@State、@Prop、@Link、@Provide、@Consume或者@ObjectLink装饰的数据。
与源对象同步双向
可以初始化子组件允许,可用于初始化常规变量、@State、@Prop、@Link、@Provide

观察变化和行为表现

观察变化

@Observed装饰的类,如果其属性为非简单类型,比如class、Object或者数组,也需要被@Observed装饰,否则将观察不到其属性的变化。 @ObjectLink:@ObjectLink只能接收被@Observed装饰class的实例,推荐设计单独的自定义组件来渲染每一个数组或对象。此时,对象数组或嵌套对象(属性是对象的对象称为嵌套对象)需要两个自定义组件,一个自定义组件呈现外部数组/对象,另一个自定义组件呈现嵌套在数组/对象内的类对象。可以观察到:

  • 其属性的数值的变化,其中属性是指Object.keys(observedObject)返回的所有属性,示例请参考嵌套对象
  • 如果数据源是数组,则可以观察到数组item的替换,如果数据源是class,可观察到class的属性的变化,示例请参考对象数组

继承Date的class时,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。
继承Map的class时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。详见继承Map类

继承Set的class时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。详见继承Set类。

框架行为

  1. 初始渲染:

    1. @Observed装饰的class的实例会被不透明的代理对象包装,代理了class上的属性的setter和getter方法
    2. 子组件中@ObjectLink装饰的从父组件初始化,接收被@Observed装饰的class的实例,@ObjectLink的包装类会将自己注册给@Observed class。
  2. 属性更新:当@Observed装饰的class属性改变时,会走到代理的setter和getter,然后遍历依赖它的@ObjectLink包装类,通知数据更新。