一、前言
在探索 HarmonyOS 的过程中,我们发现了许多有趣且实用的功能和特性。有些总是在不经意间或者触类旁通的找到。或者是某些开发痛点。其中,状态管理是ArkUI开发非常核心的一个东西,我们进行了大量的使用和测试遇到了许多奇奇怪怪的问题。
该系列将着重分享、介绍HarmonyOS API11+的新版本特性或者奇奇怪怪的解决方案、BUG。(弃用API非必要不提及)
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏
二、@State
这应该是用的最多的了,状态变量,将状态变量和UI的某个属性绑定,即可同步两者。
-
初始化行为
组件初始化时,
@State
只能从外面传入一次,之后父组件值的修改并不会影响到组件内部的@State,所以它叫组件内状态~(当然你组件用if、else来更新当我没说)@Entry @Component export struct ExpComponent { @State title: string = "Title" build() { Column() { ExpChildComponent({ childTitle: this.title }) Button(this.title) .onClick(()=>{ this.title = "Click" }) } } } @Component export struct ExpChildComponent { @State childTitle: string = "????" build() { Text(this.childTitle) } }
就像这样,
onClick
触发后ExpChildComponent
并不会发生改变了。 -
太多的@State
- 一开始我们会直接将状态变量直接声明在组件中,如:
@Component export struct ExpComponent { @State title: string = "" build() { Text(this.title) } }
但是随着,一个页面开始变得复杂的时候,我们会面临一堆的Controller,xxState等等,看起来就很头疼。这时候利用@State能观察到
Object.keys(自身)
返回的所有属性的特性,我们可以抽出一个ExpUIState
。@Component export struct ExpComponent { @State uiState: ExpUIState = new ExpUIState() build() { Text(this.uiState.title) } } class ExpUIState { title: string = "" }
如此,还你一个优雅的组件。
-
数组
@State能直接修饰数组:
@State title: Model[] = [new Model(1), new Model(2)];
数组自身的赋值可以观察到。
this.title = [new Model(2)];
数组项的赋值可以观察到。
this.title[0] = new Model(2);
删除数组项可以观察到。
this.title.pop();
新增数组项可以观察到。
this.title.push(new Model(12));
数组项中属性的赋值观察不到。
this.title[0].value = 6;
数组对象及其子项的赋值是可以被观察到的,所以“titles[0].value = 1 ”这种使用方法是没用滴。
-
作用域的影响
得益于闭包的特性(对于Java、Kotlin开发来说不存在的东西)箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。
@Component export struct ExpComponent { @State uiState: ExpUIState = new ExpUIState() build() { Column() { Text(this.uiState.title) Button("Click").onClick(()=>{ this.uiState.autoRefreshTitle() }) } } } class ExpUIState { title: string = "??" autoRefreshTitle = () => { this.title = "AutoRefreshTitle" } }
上述操作,触发
onClick
是不会更新UI的,如果想要触发更新需要用下面的例子:@Component export struct ExpComponent { @State uiState: ExpUIState = new ExpUIState() aboutToAppear(): void { } build() { Column() { Text(this.uiState.title) Button("Click").onClick(()=>{ let fk = this.uiState this.uiState.autoRefreshTitle(fk) }) } } } class ExpUIState { title: string = "??" autoRefreshTitle = (st:ExpUIState) => { st.title= "AutoRefreshTitle" } }
反人类吧?阿弥陀佛。
-
嵌套对象
@State
修饰对象时,里面可能还有对象,或者数组.class ExpUIState { childs: ExpChild[] = [] firstChild: ExpChild = new ExpChild() }
很可惜的是,由于
@State
装饰的变量,只能监听到对象本身的地址以及第一层属性的地址变化,也就是firstChild
或者childs
地址的变化,例如如下操作:uiState.firstChild.subTtile=""
,uiState.childs.push(new ExpChild())
等是没法触发刷新的。解决办法就是不用@State
三、@Prop
-
初始化行为
几乎和
@State
一致,但是@Prop
变量允许在本地修改,但修改后的变化不会同步回父组件。当数据源更改时,@Prop
装饰的变量都会更新,并且会覆盖本地所有更改。因此,数值的同步是父组件到子组件(所属组件),子组件数值的变化不会同步到父组件。相比起
@State
重点就是父组件修改后子组件会同步。苦恼@State
没法修改的人,有福了~ -
套一堆的@Prop
不要这么做。如果你的子组件使用
@Prop
、孙子组件也用@Prop
,我其实推荐你使用@Provide
(当然你硬要用也行) -
@Require
使用
@Prop
有一个好处就是,可以使用@Require
。变成一个必传的参数(福音啊)。@State
是可以不传的。@Require @Prop index: number
-
@Observed
坏消息:
@Prop
也没法观察嵌套对象~,因为父组件传入时,用的@State
传入的。好消息:加一个@Observed
就好了~@Component export struct ExpComponent { @State uiState: ExpUIState = new ExpUIState() aboutToAppear(): void { } build() { Column() { ExpChildComponent({ child: this.uiState.firstChild }) Button("Click").onClick(() => { this.uiState.firstChild.subTitle = "????" }) } } } @Component export struct ExpChildComponent { @Require @Prop child: ExpChild build() { Text(this.child.subTitle) } } @Observed class ExpUIState { childs: ExpChild[] = [] firstChild: ExpChild = new ExpChild() } @Observed class ExpChild { subTitle: string = "啊" }
在嵌套场景下,每一层都要用@Observed装饰,且每一层都要被@Prop接收。
四、@Link
-
初始化行为
@Link
装饰的变量与其父组件中的数据源共享相同的值PS:从API version 9开始,
@Link
子组件从父组件初始化@State
的语法为Comp({ aLink: this.aState })
。同样Comp({aLink: $aState})
也支持(早该如此了) -
给Dialog用
如果你在某个Dialog中,想同步数据到组件,@Link是一个很好的选择:
@CustomDialog struct CustomDialog01 { @Link inputValue: string; controller: CustomDialogController; build() { Column() { Text('Change text') .fontSize(20) .margin({ top: 10, bottom: 10 }) TextInput({ placeholder: '', text: this.inputValue }) .height(60) .width('90%') .onChange((value: string) => { this.inputValue = value; }) } } } @Entry @Component struct DialogDemo01 { @State inputValue: string = 'click me'; dialogController: CustomDialogController = new CustomDialogController({ builder: CustomDialog01({ inputValue: $inputValue }) }) build() { Column() { Button(this.inputValue) .onClick(() => { this.dialogController.open(); }) .backgroundColor(0x317aff) } .width('100%') .margin({ top: 5 }) } }
当然,你也可以传个函数进去~
-
@Link套娃
就像
@Prop
一样,我很建议你使用@Provide
、@Consume
五、@Watch
@Watch
应用于对状态变量的监听,这个装饰器可以说是非常直观的一个了,就是用来观察状态变量的。-
不要在里面修改状态变量
@State @Watch("onUiStateChange") uiState: ExpUIState = new ExpUIState() onUiStateChange(){ this.uiState.firstChild = new ExpChild() }
相信你看得出来,这是一个死循环吧?
-
子组件事件传递到父组件
可通过状态同步
@Link
或@Provide
和@Consume
进行父子组件间的状态通知,结合@Watch
可以将状态变量的修改在组件间传递,实现类似的功能。 -
粘性
@Watch
没有粘性,所以第一次初始化并不会触发@Watch! -
日志
@Watch可以观察状态变量变化的特性,很适合来做Log日志不是吗~
六、预告
下篇继续分享@State、@Prop、@Provide、@ObjectLink、@ObjectLinkV2、@Link、@Watch、@Track(可能)
七、结尾
没了。
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏
-