
鸿蒙-状态管理V1 原创
目录
前言
随着鸿蒙Next的推广,做鸿蒙开发的人是越来越多,提问和寻求帮助的人也是越来越多,就我自己回答的问题而言,大部分和状态管理相关,比如List刷新问题,,还有一些录音录像拍照问题。也不是太难的问题,需要特别仔细的阅读官方文档,有些问题的解决方法还分散在好几个文档里面,文档上也没有对一些关键点做特别讲解。这里就最常见的问题总结一下,希望后来的朋友少走一些弯路。
状态管理V1
组件的状态管理一共就这几个:
@State装饰器:组件内状态
@Prop装饰器:父子单向同步
@Link装饰器:父子双向同步
@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
@Provide装饰器和@Consume装饰器:与后代组件双向同步
其中
@Provide和@Consume群里也几乎没有人问过,姑且认为是大家都比较清楚应该怎么使用或者用的较少
@State装饰器
初始化
必须本地初始化。如果从父组件初始化,并且从父组件传入的值非undefined
,将会覆盖本地初始化;如果从父组件传入的值为undefined
,则初值为@State
装饰变量自身的初值。
也就是说无论父组件是否传值,被@State
修饰的变量都必须要指定初始值,即使被@Require
修饰也必须指定初始值。
观察能力
能观察到简单对象的变化;能观察到class 或者 Object 的赋值变化;能观察到其属性赋值的变化,但无法观察嵌套类属性赋值的变化;
看个例子,假设我们的数据类是这样的:
我们的业务逻辑姑且简化为这样
可以看到,改变简单数据后,UI 直接刷新了。修改对象中的属性,UI 也刷新了,但是在改变嵌套类属性时(改变innerClassName,UI不刷新),UI 没有刷新,也就是说改变Object.keys(observedObject)
返回的所有属性时,UI都可以刷新。
同样的,对于数组类型来讲:数组的整体赋值,数组项的赋值,调用 pop 和 push 也能观察到。
对于Map
来讲:可以观察到Map
整体的赋值,同时可通过调用Map
的接口set
, clear
, delete
更新Map的值。
对于Set
来讲:可以观察到Set
整体的赋值,同时可通过调用Set
的接口add
, clear
, delete
更新Set的值。
这里就不再过多赘述。
小坑
但这里有个比较麻烦的地方:当在build方法内,当@State装饰的变量是Object类型、且通过a.b(this.object)形式调用时,修改对象中的属性后,UI 无法刷新
比如有一个 Person 类,有个 age 属性,我们定义一个方法用来修改 age 属性。我能想到的大概有这么 5 种方式
我们来试一下:
会发现只有方法 4 和方法 5 才能使得UI 刷新,这是因为方法内传过去的是原生对象,修改其属性后不能触发刷新。如果我们有一些理由非得这么做,可以先赋值给一个临时变量,再将这个临时变量传入方法中,就可以刷新 UI 了。
没想到吧,哈哈哈哈哈
@Prop装饰器 和 @Link装饰器
这两个装饰器的观察能力和坑点和@State 相同,只不过是用来自定义的子组件中。
其中@Prop装饰器是父组件向子组件同步数据:子组件改变数据不会同步到父组件,但父组件修改数据会同步到子组件。也就是说父组件的修改会覆盖掉子组件对变量的修改,并且被它装饰的变量会进行深拷贝,在拷贝的过程中除了基本类型、Map、Set、Date、Array外,都会丢失类型。因此建议深度嵌套的数据不要太多,文档上建议不要超过 5 层。
@Link装饰器是父子组件双向同步的,但禁止本地进行初始化。
@Observed装饰器和@ObjectLink装饰器
在实际开发中,我们使用的数据模型一般都会有多层嵌套的情况,但之前介绍过的装饰器只能观察到第一层变化,无法观察到第二层变化,这种情况我们就需要使用@Observed
和@ObjectLink
装饰器.
使用示例
@Observed
装饰class。需要放在class的定义前,使用new
创建类对象。
@ObjectLink
变量装饰器只能装饰被@Observed
装饰的class实例,必须指定类型,并且不支持简单类型。
另外这两个装饰器是配套使用的,单一的使用某个装饰器无法实现观察,并且@ObjectLink
只能装饰被@Observed
装饰的类对象变量,否则会报错
The ‘@ObjectLink’ decorated attribute ‘education’ must be an ‘@Observed’ decorated class or a union of ‘@Observed’ decorated class and undefined or null, or both. <ArkTSCheck>
我们先定义一个数据类,来模拟一下业务逻辑:
这里还有一点需要注意的,到目前为止@ObjectLink
必须在自定义组件中使用。我们这里定义两个组件,用来展示Address
和Education
.
简单的写个页面,看下效果。
可以看到,点击修改 name
和修改address.location
都可以引起 UI 刷新,但点击修改education.degree
UI不会刷新。
另外还有一点需要注意的:被@Observed 修饰的嵌套类,不能跨嵌套层观察。举个例子:我们有一个嵌套的三层的数据类,我们是无法在第二层的自定义控件中观察到第三层数据的变化。示例如下:
然后我们定义两个用来展示SecondLevel
和ThirdLevel
的控件
接着写个页面试一下
可以看到,在对应的层级观察对应的嵌套类是生效的,但当我们点击修改 thirdLevel.name
时,只有在ThirdLevelView
中的控件刷新了,在SecondLevelView
中观察thirdLevel.name
的 Text内容并没有刷新。这是因为在SecondLevelView
中,@ObjectLink
仅能观察到其代理的secondLevel:SecondLevel
对象的属性变化,而secondLevel.thirdLevel.name
是ThirdLevel
的属性,无法观察到嵌套类的变化。
小结
- 不建议在被@Observed 修饰的类的构造函数中修改值,不会引起UI刷新,因为修改的是原始对象的值,并非是代理对象。
- 被
@Observed
修饰的嵌套类,不要跨嵌套层观察,建议一个数据类对应一个自定义控件。 - 必须使用new 创建的类对象,使用字面量对象无法被观察。比如网络请求返回的数据转成 JSON 后使用 as 强转为对应类型
- 通过a.b(this.object)形式调用时,修改对象中的属性后,UI 无法刷新,原因同@State
- @ObjectLink的数据源更新依赖其父组件,当父组件中数据源改变引起父组件刷新时,会重新设置子组件@ObjectLink的数据源。这个过程不是在父组件数据源变化后立刻发生的,而是在父组件实际刷新时才会进行。
