一、介绍
资料来自官网:文档中心
在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制。
- View(UI):UI渲染,指将build方法内的UI描述和@Builder装饰的方法内的UI描述映射到界面。
- State:状态,指驱动UI更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染。
二、@State装饰器:组件内状态
@State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。
说明:
@State装饰器标记的变量必须初始化,不能为空值
@State支持Object、class、string、number、boolean、enum类型以及这些类型的数组
嵌套类型以及数组中的对象属性无法触发视图更新
组件传值代码示例,为下面不同组件之间传值做准备:👇
// 任务类
class Task{static id: number = 1// 任务名称name: string = `任务${Task.id++}`// 任务状态:是否完成finished: boolean = false
}
// 统一的卡片样式
@Styles function card(){.width('95%').padding(20).backgroundColor(Color.White).borderRadius(15).shadow({radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4})
}@Entry
@Component
struct PropLinkPages {// 总任务数量@State totalTask: number = 0// 已完成任务数量@State finishTask: number = 0// 任务数组@State tasks: Task[] = []//此函数是更新任务总数量和已完成任务数量的handleTaskChange(){// 1.更新任务总数量this.totalTask = this.tasks.length// 2.更新已完成任务数量this.finishTask = this.tasks.filter(item => item.finished).length}build() {Column({space:10}){//1.任务进度卡片Row(){Text('任务进度:').fontSize(30).fontWeight(FontWeight.Bold)Stack(){Progress({value: this.finishTask,total: this.totalTask,type: ProgressType.Ring}).width(100)Row(){Text(this.finishTask.toString()).fontSize(24).fontColor('#36D')Text(' / ' + this.totalTask.toString()).fontSize(24)}}}.card().margin({top: 5, bottom: 10}).justifyContent(FlexAlign.SpaceEvenly)// 2.新增任务按钮Button('新增任务').width(200).margin({bottom: 10}).onClick(() => {// 1.新增任务数据this.tasks.push(new Task())// 2.更新任务总数量this.handleTaskChange()})//3.任务列表List({space: 10}){ForEach(this.tasks,(item: Task, index) => {ListItem(){Row(){Text(item.name).fontSize(20)Checkbox().select(item.finished).onChange(val => {// 1.更新当前任务状态item.finished = val// 2.更新已完成任务数量this.handleTaskChange()})}.card().justifyContent(FlexAlign.SpaceBetween)}.swipeAction({end: this.DeleteButton(index)})})}.width('100%').layoutWeight(1).alignListItem(ListItemAlign.Center)}.width('100%').height('100%').backgroundColor('#F1F2F3')}@Builder DeleteButton(index: number){Button(){Image($r('app.media.ic_public_delete_filled')).fillColor(Color.White).width(20)}.width(40).height(40).type(ButtonType.Circle).backgroundColor(Color.Red).margin(5).onClick(() => {this.tasks.splice(index, 1)this.handleTaskChange()})}
}
示例代码说明:
这是一个展示任务进度的效果,分为任务进度条和任务列表两部分
对于新增的任务勾选后可在任务进度中查看已勾选和总任务数量,左滑单个任务会出现删除按钮,可进行此任务删除操作
示例代码的效果:
三、父子组件数据同步
3.1、@Prop装饰器:父子单向同步
@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。
需求:将示例代码中的任务进度卡片封装成TaskStatistics组件,在PropLinkPages组件中引入TaskStatistics组件,封装后再完成数据的同步渲染
上面示例中:
父组件PropLinkPages,子组件TaskStatistics
总任务与已完成任务数据是由父组件进行维护,子组件进行渲染,所以需要父组件将数据传递给子组件
✍使用@Prop,父子单向同步
@Prop只支持string、number、boolean、enum类型;父组件对象类型,子组件是对象属性;不可以是数组、any
3.2、@Link装饰器:父子双向同步
@Link装饰的变量与其父组件中的数据源共享相同的值。
限制条件:@Link装饰器不能在@Entry装饰的自定义组件中使用
需求:将示例代码中对任务数组的操作(新增任务与任务列表)封装成TaskList组件,在PropLinkPages组件中引入TaskList组件
上面示例中:
父组件PropLinkPages,子组件TaskList
父子双方都需要使用总认为与已完成任务数据,并且子组件的数据发生变化后需要通知父组件进行变化,因为上一步@Prop时父组件需要将数据传递给另一个子组件TaskStatistics,所以涉及到父子双向数据绑定渲染
✍使用@Link,父子双向同步
父子类型一致:string、number、boolean、enum、object、class,以及他们的数组;
数组中元素增、删、替换会引起刷新
嵌套类型以及数组中的对象属性无法触发视图更新
四、后代组件双向同步
4.1、@Provide装饰器和@Consume装饰器:与后代组件双向同步
@Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。
需求:示例代码中分别使用@Prop与@Link进行数据传递,需要更改为@Provide和@Consume跨组件数据传递
上面示例中:
父组件PropLinkPages,子组件TaskList,子组件TaskStatistics
在父组件中使用@Provide将所需数据传给两个子组件,两个子组件通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步
✍@Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,变量类型必须相同。
五、嵌套类对象属性变化
5.1、@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink装饰器。
限制条件:
a:使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。
b:@ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
需求: 改造任务进度的代码,当任务完成后,此任务置灰,并有中划线
实现步骤:
①任务数组对应的元素Task是对象类型,给Task对象添加@Observed装饰器
②给嵌套的对象上所对应的变量上添加@ObjectLink装饰器,但源代码中是方法参数,所以将此段代码封装为TaskItem组件,在TaskItem组件中对变量item添加@ObjectLink
问题:子组件需要调父组件的方法,把父组件的方法作为参数传递过来,传递过程中存在this的丢失
解决:子组件中定义onTaskChange方法,传递给父组件时对函数使用bind方法将this传递进去
如下:TaskItem({item:item,onTaskChange:this.handleTaskChange.bind(this)})
// 任务类
@Observed
class Task{static id: number = 1// 任务名称name: string = `任务${Task.id++}`// 任务状态:是否完成finished: boolean = false
}
// 统一的卡片样式
@Styles function card(){.width('95%').padding(20).backgroundColor(Color.White).borderRadius(15).shadow({radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4})
}
// 任务完成样式
@Extend(Text) function finishedTask(){.decoration({type:TextDecorationType.LineThrough}).fontColor('#B1B2B1')
}@Entry
@Component
struct PropLinkPages {// 总任务数量@Provide totalTask: number = 0// 已完成任务数量@Provide finishTask: number = 0build() {Column({space:10}){//1.任务进度卡片TaskStatistics()//2.任务列表TaskList()}.width('100%').height('100%').backgroundColor('#F1F2F3')}
}@Component
struct TaskList {// 任务数组@State tasks: Task[] = []@Consume totalTask: number@Consume finishTask: number//此函数是更新任务总数量和已完成任务数量的handleTaskChange(){// 1.更新任务总数量this.totalTask = this.tasks.length// 2.更新已完成任务数量this.finishTask = this.tasks.filter(item => item.finished).length}build() {Column(){// 2.新增任务按钮Button('新增任务').width(200).margin({bottom: 10}).onClick(() => {// 1.新增任务数据this.tasks.push(new Task())// 2.更新任务总数量this.handleTaskChange()})//3.任务列表List({space: 10}){ForEach(this.tasks,(item: Task, index) => {ListItem(){TaskItem({item:item,onTaskChange:this.handleTaskChange.bind(this)})}.swipeAction({end: this.DeleteButton(index)})})}.width('100%').layoutWeight(1).alignListItem(ListItemAlign.Center)}}@Builder DeleteButton(index: number){Button(){Image($r('app.media.ic_public_delete_filled')).fillColor(Color.White).width(20)}.width(40).height(40).type(ButtonType.Circle).backgroundColor(Color.Red).margin(5).onClick(() => {this.tasks.splice(index, 1)this.handleTaskChange()})}
}@Component
struct TaskStatistics {@Consume totalTask: number@Consume finishTask: numberbuild() {Row(){Text('任务进度:').fontSize(30).fontWeight(FontWeight.Bold)Stack(){Progress({value: this.finishTask,total: this.totalTask,type: ProgressType.Ring}).width(100)Row(){Text(this.finishTask.toString()).fontSize(24).fontColor('#36D')Text(' / ' + this.totalTask.toString()).fontSize(24)}}}.card().margin({top: 5, bottom: 10}).justifyContent(FlexAlign.SpaceEvenly)}
}@Component
struct TaskItem {@ObjectLink item: TaskonTaskChange: () => voidbuild() {Row(){if(this.item.finished){Text(this.item.name).finishedTask()}else{Text(this.item.name)}Checkbox().select(this.item.finished).onChange(val => {// 1.更新当前任务状态this.item.finished = val// 2.更新已完成任务数量this.onTaskChange()})}.card().justifyContent(FlexAlign.SpaceBetween)}
}
实现效果:
最后:👏👏😊😊😊👍👍