HarmonyOS ArkUI实战开发—状态管理

news/2024/5/8 20:29:49

一、状态管理

在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制。

自定义组件拥有变量,变量必须被装饰器装饰才可以成为状态变量,状态变量的改变会引起UI的渲染刷新。如果不使用状态变量,UI只能在初始化时渲染,后续将不会再刷新。下图展示了State和View(UI)之间的关系。

说明如下:

  • View(UI):UI渲染,一般指自定义组件的build方法和@Builder装饰的方法内的UI描述。
  • State:状态,一般指的是装饰器装饰的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染。

二、@State修饰符

@State 装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的 build() 方法刷新UI。 @State 状态数据具有以下特征:

  • @State装饰器标记的变量必须初始化,不能为空值
  • @state支持object、class、string、number、boolean、enum类型以及这些类型的数组
  • 嵌套类型以及数组中的对象属性无法触发视图更新
  • 标记为 @State 的属性是私有变量,只能在组件内访问。

2.1.@State修饰符案例

创建StateExample01.ets,代码如下:

@Entry
@Component
struct StateExample01 {@State message:string = "Hello World"build() {Column(){Text(this.message).fontSize(50).onClick(()=>{//变量通过@State修饰,点击修改私有变量,然后会自动修改刷新UIthis.message = "Hi Augus"})}.width("100%").height("100%").justifyContent(FlexAlign.Center)//主轴方向对齐}
}

预览效果如下:

2.2.@state修饰的私有变量类型

@state支持object、class、string、number、boolean、enum类型以及这些类型的数组,下面演示,点击修改Sutdent对象的年龄属性,点击一次,页面重新渲染一次:

class Student{sid:numbername:stringage:numberconstructor(sid:number,name:string,age:number) {this.sid = sidthis.name = namethis.age = age}
}@Entry
@Component
struct StateExample02{//私有变量的值是一个对象@State s:Student = new Student(2301,"马保国", 73)//@State必须初始化。否则会报错//@State s:Studentbuild() {Column(){Text(`${this.s.sid}:${this.s.name}:${this.s.age}`).fontSize(30).onClick(()=>{//变量通过@State修饰,点击修改私有变量(点击一次自增1),然后会自动修改刷新UIthis.s.age++})}.width("100%").height("100%").justifyContent(FlexAlign.Center)//主轴方向对齐}
}

预览效果如下:

2.3.嵌套类型的对象属性无法触发视图更新

下面的案例中Student对象嵌套了一个Pet对象,当修改Pet对象属性的时候,是无法触发视图的更新,下面的代码中,点击的时候虽然数据修改了,点击界面并没有修改,代码如下:

class Student{sid:numbername:stringage:number//宠物pet:Petconstructor(sid:number,name:string,age:number,pet:Pet) {this.sid = sidthis.name = namethis.age = agethis.pet = pet}
}//宠物
class Pet{petName:stringpetAge:numberconstructor(petName:string,petAge:number) {this.petName = petNamethis.petAge = petAge}
}@Entry
@Component
struct StateExample03{//私有变量的值是一个对象@State s:Student = new Student(2301,"马保国", 73, new Pet("大黄",3))//@State必须初始化。否则会报错//@State s:Studentbuild() {Column(){//修改Student的属性是可以的Text(`${this.s.sid}:${this.s.name}:${this.s.age}`).fontSize(30).onClick(()=>{//变量通过@State修饰,点击修改私有变量(点击一次自增1),然后会自动修改刷新UIthis.s.age++})//修改Student的中包含的pet对象的属性值,@State装饰器是做不到的Text(`${this.s.pet.petName}:${this.s.pet.petAge}`).fontSize(30).onClick(()=>{//点击修改变属性的值this.s.pet.petAge++})}.width("100%").height("100%").justifyContent(FlexAlign.Center)//主轴方向对齐}
}

预览效果如下:

2.4.数组中的对象属性无法触发视图更新

class Student{sid:numbername:stringage:number//宠物pet:Petconstructor(sid:number,name:string,age:number,pet:Pet) {this.sid = sidthis.name = namethis.age = agethis.pet = pet}
}//宠物
class Pet{petName:stringpetAge:numberconstructor(petName:string,petAge:number) {this.petName = petNamethis.petAge = petAge}
}@Entry
@Component
struct StateExample03{//私有变量的值是一个对象@State s:Student = new Student(2301,"马保国", 73, new Pet("大黄",3))//准备一个数组@State pets:Pet[] = [new Pet("小白",2300), new Pet("小痴", 1100)]build() {Column({space:20}){//修改Student的属性是可以的Text(`${this.s.sid}:${this.s.name}:${this.s.age}`).fontSize(30).onClick(()=>{//变量通过@State修饰,点击修改私有变量(点击一次自增1),然后会自动修改刷新UIthis.s.age++})//添加宠物Button("添加").onClick(()=>{this.pets.push(new Pet("小灰"+1, 10))})Text("---------宠物列表------").fontSize(30).width("100%")ForEach(this.pets,(pet:Pet, index)=>{Row(){Text(`${pet.petName}:${pet.petAge}`).fontSize(20)Button("修改年龄").onClick(()=>{//点击后发现修改了数据,但是由于属性属于数组的对象,@State无法让修改后自动渲染pet.petAge++})}.width("100%").justifyContent(FlexAlign.SpaceAround)})}.width("100%").height("100%").justifyContent(FlexAlign.Center)//主轴方向对齐}
}

点击修改的年龄是属于,pets数组中对象的属性,使用@State装饰器无法触发视图的渲染,点击页面无法更新,预览效果如下:

三、案例练习

这里实现如下效果,作为后续装饰器讲解的案例代码。

代码如下:

//任务类
class Task{static  id:number = 1;//任务名称,id每次增加1name:string = `任务${Task.id++}`//任务状态,是否完成taskStatus:boolean = false
}//统一的卡片样式
@Styles function  card(){.width("90%").padding(20).backgroundColor(Color.White).borderRadius(15)//为当前组件添加阴影效果.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}@Entry
@Component
struct StatusManagement {//总任务数量@State totalTask:number = 0//已完成数量@State finishTask:number = 0//保存添加任务的数组@State tasks: Task[] = []//将跟新数据的操作进一步抽取DataUpdate(){//需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length//跟新已完成任务总数this.finishTask = this.tasks.filter(item=> item.taskStatus).length}//自定义删除删除@Builder DeleteTaskButton(index:number){Button(){Image($r("app.media.icon_remove_button")).width(20).fillColor("#B0E0E6")}.width(40).height(40).type(ButtonType.Circle).onClick(()=>{//去数组中删除this.tasks.splice(index, 1)/*//需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length//跟新已完成任务总数this.finishTask = this.tasks.filter(item=> item.taskStatus).length*///上面的更新数据进一步封装,然后调用this.DataUpdate()}).backgroundColor(Color.Red).margin(10)}build() {Column({space:20}){//1.任务进度Row(){Text("任务进度:").fontSize(30) //字体大小.fontWeight(FontWeight.Bold)//字体加粗//环形和数字要使用堆叠容器,Stack(){//环形组件: 进度、总量、样式Progress({value:this.finishTask, total:this.totalTask,type:ProgressType.Ring}).width(90)Row(){//让数字显示在一起,放在一个容器中//任务完成量Text(`${this.finishTask}`).fontSize(25) //字体大小.fontColor("#0000CD")//任务总量Text(` / ${this.totalTask}`).fontSize(25) //字体大小}}}.width("100%").margin({top:20,bottom:20}).justifyContent(FlexAlign.SpaceAround) //主轴方向布局.card()//2.添加任务按钮Button("添加任务").width(200).onClick(()=>{//1.添加任务,就是给任务数组中添加一个值this.tasks.push(new Task())//2.新增任务后,需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length})//3.任务列表List({space:5}){ForEach(this.tasks,(item:Task, index:number)=>{ListItem(){Row(){//文本Text(item.name).fontColor(20)//单选框,select决定是否选中,类型布尔值,取Task对象属性taskStatusCheckbox().select(item.taskStatus).onChange((value:boolean)=>{//1.更新当前已完成任务状态,勾选后修改状态为trueitem.taskStatus = value/*//2.统计已完成的数量,就是统计数组中状态为true的元素个数this.finishTask = this.tasks.filter(item=> item.taskStatus).length*///上面的更新数据进一步封装,然后调用this.DataUpdate()})}.width("100%").card().justifyContent(FlexAlign.SpaceBetween)}/*** 用于设置ListItem的划出组件。* - start: ListItem向右划动时item左边的组件(List垂直布局时)或ListItem向下划动时item上方的组件(List水平布局时)。* - end: ListItem向左划动时item右边的组件(List垂直布局时)或ListItem向上划动时item下方的组件(List水平布局时)。* - edgeEffect: 滑动效果。*/.swipeAction({end: this.DeleteTaskButton(index)})})}.width("100%").layoutWeight(1) //忽略元素本身尺寸设置,表示自适应占满剩余空间。.alignListItem(ListItemAlign.Center) //ListItem在List交叉轴方向的布局方式(这里就是水平方向居中对齐),默认为首部对齐。}.size({width:"100%",height:"100%"}).backgroundColor("#F0F8FF")}
}

四、@Prop和@Link

之前章节的时候,我们吧所有的代码都写在一起,这样会导致代码的可读性很差,所以我们会把功能封装成不同的组件

这时候在父子组件需要进行数据同步的时候,可以通过@Prop和@Link装饰器来做到。在父组件中用@State装饰,在自组件中用@Prop或@Link装饰。

说明:

  • @Prop用于子组件只监听父组件的数据改变而改变,自己不对数据改变,俗称单项同步
  • @Link用于子组件与父组件都会对数据改变,都需要在数据改变的时候发生相应的更新。俗称双向同步

4.1.@Prop装饰器

将章节二中的代码,数据统计和展示分别抽取成两个子组件,这里先抽取出来数据统计部分,代码如下:

//任务类
class Task{static  id:number = 1;//任务名称,id每次增加1name:string = `任务${Task.id++}`//任务状态,是否完成taskStatus:boolean = false
}//统一的卡片样式
@Styles function  card(){.width("90%").padding(20).backgroundColor(Color.White).borderRadius(15)//为当前组件添加阴影效果.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}@Entry
@Component
struct StatusManagement {//总任务数量@State totalTask:number = 0//已完成数量@State finishTask:number = 0//保存添加任务的数组@State tasks: Task[] = []//将跟新数据的操作进一步抽取DataUpdate(){//需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length//跟新已完成任务总数this.finishTask = this.tasks.filter(item=> item.taskStatus).length}//自定义删除删除@Builder DeleteTaskButton(index:number){Button(){Image($r("app.media.icon_remove_button")).width(20).fillColor("#B0E0E6")}.width(40).height(40).type(ButtonType.Circle).onClick(()=>{//去数组中删除this.tasks.splice(index, 1)/*//需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length//跟新已完成任务总数this.finishTask = this.tasks.filter(item=> item.taskStatus).length*///上面的更新数据进一步封装,然后调用this.DataUpdate()}).backgroundColor(Color.Red).margin(10)}build() {Column({space:20}){//1.任务进度 这里直接调用自定义的组件TaskStatusProgress({totalTask:this.totalTask, finishTask: this.finishTask})//2.添加任务按钮Button("添加任务").width(200).onClick(()=>{//1.添加任务,就是给任务数组中添加一个值this.tasks.push(new Task())//2.新增任务后,需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length})//3.任务列表List({space:5}){ForEach(this.tasks,(item:Task, index:number)=>{ListItem(){Row(){//文本Text(item.name).fontColor(20)//单选框,select决定是否选中,类型布尔值,取Task对象属性taskStatusCheckbox().select(item.taskStatus).onChange((value:boolean)=>{//1.更新当前已完成任务状态,勾选后修改状态为trueitem.taskStatus = value/*//2.统计已完成的数量,就是统计数组中状态为true的元素个数this.finishTask = this.tasks.filter(item=> item.taskStatus).length*///上面的更新数据进一步封装,然后调用this.DataUpdate()})}.width("100%").card().justifyContent(FlexAlign.SpaceBetween)}/*** 用于设置ListItem的划出组件。* - start: ListItem向右划动时item左边的组件(List垂直布局时)或ListItem向下划动时item上方的组件(List水平布局时)。* - end: ListItem向左划动时item右边的组件(List垂直布局时)或ListItem向上划动时item下方的组件(List水平布局时)。* - edgeEffect: 滑动效果。*/.swipeAction({end: this.DeleteTaskButton(index)})})}.width("100%").layoutWeight(1) //忽略元素本身尺寸设置,表示自适应占满剩余空间。.alignListItem(ListItemAlign.Center) //ListItem在List交叉轴方向的布局方式(这里就是水平方向居中对齐),默认为首部对齐。}.size({width:"100%",height:"100%"}).backgroundColor("#F0F8FF")}
}/*** 定义任务进度组件* 使用@Prop装饰器,监控父组件的数据状态,而改变自身的数据*/
@Component
struct TaskStatusProgress {//TODO “@Prop”、“@Link”修饰的变量不允许在本地初始化//总任务数量@Prop totalTask:number//已完成数量@Prop finishTask:numberbuild() {//1.任务进度Row(){Text("任务进度:").fontSize(30) //字体大小.fontWeight(FontWeight.Bold)//字体加粗//环形和数字要使用堆叠容器,Stack(){//环形组件: 进度、总量、样式Progress({value:this.finishTask, total:this.totalTask,type:ProgressType.Ring}).width(90)Row(){//让数字显示在一起,放在一个容器中//任务完成量Text(`${this.finishTask}`).fontSize(25) //字体大小.fontColor("#0000CD")//任务总量Text(` / ${this.totalTask}`).fontSize(25) //字体大小}}}.width("100%").margin({top:20,bottom:20}).justifyContent(FlexAlign.SpaceAround) //主轴方向布局.card()}
}

上面的代码将任务进度抽取成组件TaskStatusProgress ,然后在调用即可,但是需要注意的是,作为子组件TaskStatusProgress ,只需要监控父组件的任务总量和已完成任务的值,然后自己进行渲染即可,并不需要改变数据,所以在TaskStatusProgress 子组件中定义任务总量和任务进度变量的时候,使用@Prop装饰器。

4.2.@Link装饰器

将新增任务按钮和任务列表抽取成第二个子组件TaskList,由于TaskList子组件本身需要修改数据(任务总量和已完成任务进度),同时父组件需要感知到子组件的修改,将数据传入到上一章节定义TaskStatusProgress子组件中,进行数据展示,所以这是一个双向的数据同步,需要在子组件中定义变量任务总量和已完成任务的时候使用@Link装饰器实现双向的数据同步。但是需要注意的是,在父组件调用TaskLink子组件的时候,传入参数的时候需要使用$,同时不能使用this,才可以如下:

//2.任务列表
TaskList({totalTask: $totalTask, finishTask:$finishTask})

子组件TaskList如下:

/*** 定义任务列表子组件*/
@Component
struct TaskList {//总任务数量@Link totalTask:number//已完成数量@Link finishTask:number//保存添加任务的数组@State tasks: Task[] = []//将跟新数据的操作进一步抽取DataUpdate(){//需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length//跟新已完成任务总数this.finishTask = this.tasks.filter(item=> item.taskStatus).length}//自定义删除删除@Builder DeleteTaskButton(index:number){Button(){Image($r("app.media.icon_remove_button")).width(20).fillColor("#B0E0E6")}.width(40).height(40).type(ButtonType.Circle).onClick(()=>{//去数组中删除this.tasks.splice(index, 1)/*//需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length//跟新已完成任务总数this.finishTask = this.tasks.filter(item=> item.taskStatus).length*///上面的更新数据进一步封装,然后调用this.DataUpdate()}).backgroundColor(Color.Red).margin(10)}build() {Column(){//2.添加任务按钮Button("添加任务").width(200).onClick(()=>{//1.添加任务,就是给任务数组中添加一个值this.tasks.push(new Task())//2.新增任务后,需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length})//3.任务列表List({space:5}){ForEach(this.tasks,(item:Task, index:number)=>{ListItem(){Row(){//文本Text(item.name).fontColor(20)//单选框,select决定是否选中,类型布尔值,取Task对象属性taskStatusCheckbox().select(item.taskStatus).onChange((value:boolean)=>{//1.更新当前已完成任务状态,勾选后修改状态为trueitem.taskStatus = value/*//2.统计已完成的数量,就是统计数组中状态为true的元素个数this.finishTask = this.tasks.filter(item=> item.taskStatus).length*///上面的更新数据进一步封装,然后调用this.DataUpdate()})}.width("100%").card().justifyContent(FlexAlign.SpaceBetween)}/*** 用于设置ListItem的划出组件。* - start: ListItem向右划动时item左边的组件(List垂直布局时)或ListItem向下划动时item上方的组件(List水平布局时)。* - end: ListItem向左划动时item右边的组件(List垂直布局时)或ListItem向上划动时item下方的组件(List水平布局时)。* - edgeEffect: 滑动效果。*/.swipeAction({end: this.DeleteTaskButton(index)})})}.width("100%").layoutWeight(1) //忽略元素本身尺寸设置,表示自适应占满剩余空间。.alignListItem(ListItemAlign.Center) //ListItem在List交叉轴方向的布局方式(这里就是水平方向居中对齐),默认为首部对齐。}.width("100%").height("100%")}
}

完整的代码如下:

//任务类
class Task{static  id:number = 1;//任务名称,id每次增加1name:string = `任务${Task.id++}`//任务状态,是否完成taskStatus:boolean = false
}//统一的卡片样式
@Styles function  card(){.width("90%").padding(20).backgroundColor(Color.White).borderRadius(15)//为当前组件添加阴影效果.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}@Entry
@Component
struct StatusManagement {//总任务数量@State totalTask:number = 0//已完成数量@State finishTask:number = 0//保存添加任务的数组//@State tasks: Task[] = []build() {Column({space:20}){//1.任务进度 这里直接调用自定义的组件TaskStatusProgress({totalTask:this.totalTask, finishTask: this.finishTask})//2.任务列表TaskList({totalTask: $totalTask, finishTask:$finishTask})}.size({width:"100%",height:"100%"}).backgroundColor("#F0F8FF")}
}/*** 定义任务进度组件* 使用@Prop装饰器,监控父组件的数据状态,而改变自身的数据*/
@Component
struct TaskStatusProgress {//TODO “@Prop”、“@Link”修饰的变量不允许在本地初始化//总任务数量@Prop totalTask:number//已完成数量@Prop finishTask:numberbuild() {//1.任务进度Row(){Text("任务进度:").fontSize(30) //字体大小.fontWeight(FontWeight.Bold)//字体加粗//环形和数字要使用堆叠容器,Stack(){//环形组件: 进度、总量、样式Progress({value:this.finishTask, total:this.totalTask,type:ProgressType.Ring}).width(90)Row(){//让数字显示在一起,放在一个容器中//任务完成量Text(`${this.finishTask}`).fontSize(25) //字体大小.fontColor("#0000CD")//任务总量Text(` / ${this.totalTask}`).fontSize(25) //字体大小}}}.width("100%").margin({top:20,bottom:20}).justifyContent(FlexAlign.SpaceAround) //主轴方向布局.card()}
}/*** 定义任务列表子组件*/
@Component
struct TaskList {//总任务数量@Link totalTask:number//已完成数量@Link finishTask:number//保存添加任务的数组@State tasks: Task[] = []//将跟新数据的操作进一步抽取DataUpdate(){//需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length//跟新已完成任务总数this.finishTask = this.tasks.filter(item=> item.taskStatus).length}//自定义删除删除@Builder DeleteTaskButton(index:number){Button(){Image($r("app.media.icon_remove_button")).width(20).fillColor("#B0E0E6")}.width(40).height(40).type(ButtonType.Circle).onClick(()=>{//去数组中删除this.tasks.splice(index, 1)/*//需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length//跟新已完成任务总数this.finishTask = this.tasks.filter(item=> item.taskStatus).length*///上面的更新数据进一步封装,然后调用this.DataUpdate()}).backgroundColor(Color.Red).margin(10)}build() {Column(){//2.添加任务按钮Button("添加任务").width(200).onClick(()=>{//1.添加任务,就是给任务数组中添加一个值this.tasks.push(new Task())//2.新增任务后,需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length})//3.任务列表List({space:5}){ForEach(this.tasks,(item:Task, index:number)=>{ListItem(){Row(){//文本Text(item.name).fontColor(20)//单选框,select决定是否选中,类型布尔值,取Task对象属性taskStatusCheckbox().select(item.taskStatus).onChange((value:boolean)=>{//1.更新当前已完成任务状态,勾选后修改状态为trueitem.taskStatus = value/*//2.统计已完成的数量,就是统计数组中状态为true的元素个数this.finishTask = this.tasks.filter(item=> item.taskStatus).length*///上面的更新数据进一步封装,然后调用this.DataUpdate()})}.width("100%").card().justifyContent(FlexAlign.SpaceBetween)}/*** 用于设置ListItem的划出组件。* - start: ListItem向右划动时item左边的组件(List垂直布局时)或ListItem向下划动时item上方的组件(List水平布局时)。* - end: ListItem向左划动时item右边的组件(List垂直布局时)或ListItem向上划动时item下方的组件(List水平布局时)。* - edgeEffect: 滑动效果。*/.swipeAction({end: this.DeleteTaskButton(index)})})}.width("100%").layoutWeight(1) //忽略元素本身尺寸设置,表示自适应占满剩余空间。.alignListItem(ListItemAlign.Center) //ListItem在List交叉轴方向的布局方式(这里就是水平方向居中对齐),默认为首部对齐。}.width("100%").height("100%")}
}

4.2.变量数据类型说明

@Prop和@Link变量类型和初始化方式说明如下:

需要注意的是,数据同步的时候:

  • @Prop父组件是对象类型,则子组件是对象属性
  • @Link父子类型一致

1)Prop父组件变量是对象类型,则子组件是对象属性,这里以TaskStatusProgress任务进度子组件进行演示,因为TaskList必须是双向同步,父组件才可以知道数据变化,必须使用@Link

//任务类
class Task{static  id:number = 1;//任务名称,id每次增加1name:string = `任务${Task.id++}`//任务状态,是否完成taskStatus:boolean = false
}//统一的卡片样式
@Styles function  card(){.width("90%").padding(20).backgroundColor(Color.White).borderRadius(15)//为当前组件添加阴影效果.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}//将统计信息抽取出来形成一个类
class StateInfo{//总任务数量totalTask:number//已完成数量finishTask:numberconstructor( totalTask:number = 0,finishTask:number = 0 ) {this.totalTask = totalTaskthis.finishTask = finishTask}
}@Entry
@Component
struct StatusManagement {//TODO 父子组件变量类型是对象, @Prop子组件变量类型是对象的属性//创建统计信息对象@State stat: StateInfo = new StateInfo()build() {Column({space:20}){//1.任务进度 这里直接调用自定义的组件,使用的是@Prop,通过属性传入TaskStatusProgress({totalTask:this.stat.totalTask, finishTask: this.stat.finishTask})//2.任务列表//TODO 子组件使用的@Link, 通过$符的方式传值TaskList({stat:$stat})}.size({width:"100%",height:"100%"}).backgroundColor("#F0F8FF")}
}/*** 定义任务进度组件* 使用@Prop装饰器,监控父组件的数据状态,而改变自身的数据*/
@Component
struct TaskStatusProgress {//TODO 父组件是对象,子组件则可以使用“@Prop”作为对象的属性//总任务数量@Prop totalTask:number//已完成数量@Prop finishTask:numberbuild() {//1.任务进度Row(){Text("任务进度:").fontSize(30) //字体大小.fontWeight(FontWeight.Bold)//字体加粗//环形和数字要使用堆叠容器,Stack(){//环形组件: 进度、总量、样式Progress({value:this.finishTask, total:this.totalTask,type:ProgressType.Ring}).width(90)Row(){//让数字显示在一起,放在一个容器中//任务完成量Text(`${this.finishTask}`).fontSize(25) //字体大小.fontColor("#0000CD")//任务总量Text(` / ${this.totalTask}`).fontSize(25) //字体大小}}}.width("100%").margin({top:20,bottom:20}).justifyContent(FlexAlign.SpaceAround) //主轴方向布局.card()}
}/*** 定义任务列表子组件*/
@Component
struct TaskList {//TODO@Link stat: StateInfo//保存添加任务的数组@State tasks: Task[] = []//将跟新数据的操作进一步抽取DataUpdate(){//需要跟新一下任务总量(就是任务数组的长度)this.stat.totalTask = this.tasks.length//跟新已完成任务总数this.stat.finishTask = this.tasks.filter(item=> item.taskStatus).length}//自定义删除删除@Builder DeleteTaskButton(index:number){Button(){Image($r("app.media.icon_remove_button")).width(20).fillColor("#B0E0E6")}.width(40).height(40).type(ButtonType.Circle).onClick(()=>{//去数组中删除this.tasks.splice(index, 1)//上面的更新数据进一步封装,然后调用this.DataUpdate()}).backgroundColor(Color.Red).margin(10)}build() {Column(){//2.添加任务按钮Button("添加任务").width(200).onClick(()=>{//1.添加任务,就是给任务数组中添加一个值this.tasks.push(new Task())//2.新增任务后,需要跟新一下任务总量(就是任务数组的长度)this.stat.totalTask = this.tasks.length})//3.任务列表List({space:5}){ForEach(this.tasks,(item:Task, index:number)=>{ListItem(){Row(){//文本Text(item.name).fontColor(20)//单选框,select决定是否选中,类型布尔值,取Task对象属性taskStatusCheckbox().select(item.taskStatus).onChange((value:boolean)=>{//1.更新当前已完成任务状态,勾选后修改状态为trueitem.taskStatus = value/*//2.统计已完成的数量,就是统计数组中状态为true的元素个数this.finishTask = this.tasks.filter(item=> item.taskStatus).length*///上面的更新数据进一步封装,然后调用this.DataUpdate()})}.width("100%").card().justifyContent(FlexAlign.SpaceBetween)}/*** 用于设置ListItem的划出组件。* - start: ListItem向右划动时item左边的组件(List垂直布局时)或ListItem向下划动时item上方的组件(List水平布局时)。* - end: ListItem向左划动时item右边的组件(List垂直布局时)或ListItem向上划动时item下方的组件(List水平布局时)。* - edgeEffect: 滑动效果。*/.swipeAction({end: this.DeleteTaskButton(index)})})}.width("100%").layoutWeight(1) //忽略元素本身尺寸设置,表示自适应占满剩余空间。.alignListItem(ListItemAlign.Center) //ListItem在List交叉轴方向的布局方式(这里就是水平方向居中对齐),默认为首部对齐。}.width("100%").height("100%")}
}

2)@Link演示,父子组件变量同为对象

//任务类
class Task{static  id:number = 1;//任务名称,id每次增加1name:string = `任务${Task.id++}`//任务状态,是否完成taskStatus:boolean = false
}//统一的卡片样式
@Styles function  card(){.width("90%").padding(20).backgroundColor(Color.White).borderRadius(15)//为当前组件添加阴影效果.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}//将统计信息抽取出来形成一个类
class StateInfo{//总任务数量totalTask:number//已完成数量finishTask:numberconstructor( totalTask:number = 0,finishTask:number = 0 ) {this.totalTask = totalTaskthis.finishTask = finishTask}
}@Entry
@Component
struct StatusManagement {//TODO @Link 父子组件变量类型都可以是对象//创建统计信息对象@State stat: StateInfo = new StateInfo()build() {Column({space:20}){//1.任务进度 这里直接调用自定义的组件TaskStatusProgress({totalTask:this.stat.totalTask, finishTask: this.stat.finishTask})//2.任务列表//TODO 这里任然使用$参数名的形式TaskList({stat:$stat})}.size({width:"100%",height:"100%"}).backgroundColor("#F0F8FF")}
}/*** 定义任务进度组件* 使用@Prop装饰器,监控父组件的数据状态,而改变自身的数据*/
@Component
struct TaskStatusProgress {//TODO “@Prop”、“@Link”修饰的变量不允许在本地初始化//总任务数量@Prop totalTask:number//已完成数量@Prop finishTask:numberbuild() {//1.任务进度Row(){Text("任务进度:").fontSize(30) //字体大小.fontWeight(FontWeight.Bold)//字体加粗//环形和数字要使用堆叠容器,Stack(){//环形组件: 进度、总量、样式Progress({value:this.finishTask, total:this.totalTask,type:ProgressType.Ring}).width(90)Row(){//让数字显示在一起,放在一个容器中//任务完成量Text(`${this.finishTask}`).fontSize(25) //字体大小.fontColor("#0000CD")//任务总量Text(` / ${this.totalTask}`).fontSize(25) //字体大小}}}.width("100%").margin({top:20,bottom:20}).justifyContent(FlexAlign.SpaceAround) //主轴方向布局.card()}
}/*** 定义任务列表子组件*/
@Component
struct TaskList {//TODO @Link 父子组件变量类型都可以是对象//总任务数量@Link stat:StateInfo//保存添加任务的数组@State tasks: Task[] = []//将跟新数据的操作进一步抽取DataUpdate(){//需要跟新一下任务总量(就是任务数组的长度)this.stat.totalTask = this.tasks.length//跟新已完成任务总数this.stat.finishTask = this.tasks.filter(item=> item.taskStatus).length}//自定义删除删除@Builder DeleteTaskButton(index:number){Button(){Image($r("app.media.icon_remove_button")).width(20).fillColor("#B0E0E6")}.width(40).height(40).type(ButtonType.Circle).onClick(()=>{//去数组中删除this.tasks.splice(index, 1)/*//需要跟新一下任务总量(就是任务数组的长度)this.totalTask = this.tasks.length//跟新已完成任务总数this.finishTask = this.tasks.filter(item=> item.taskStatus).length*///上面的更新数据进一步封装,然后调用this.DataUpdate()}).backgroundColor(Color.Red).margin(10)}build() {Column(){//2.添加任务按钮Button("添加任务").width(200).onClick(()=>{//1.添加任务,就是给任务数组中添加一个值this.tasks.push(new Task())//2.新增任务后,需要跟新一下任务总量(就是任务数组的长度)this.stat.totalTask = this.tasks.length})//3.任务列表List({space:5}){ForEach(this.tasks,(item:Task, index:number)=>{ListItem(){Row(){//文本Text(item.name).fontColor(20)//单选框,select决定是否选中,类型布尔值,取Task对象属性taskStatusCheckbox().select(item.taskStatus).onChange((value:boolean)=>{//1.更新当前已完成任务状态,勾选后修改状态为trueitem.taskStatus = value/*//2.统计已完成的数量,就是统计数组中状态为true的元素个数this.finishTask = this.tasks.filter(item=> item.taskStatus).length*///上面的更新数据进一步封装,然后调用this.DataUpdate()})}.width("100%").card().justifyContent(FlexAlign.SpaceBetween)}/*** 用于设置ListItem的划出组件。* - start: ListItem向右划动时item左边的组件(List垂直布局时)或ListItem向下划动时item上方的组件(List水平布局时)。* - end: ListItem向左划动时item右边的组件(List垂直布局时)或ListItem向上划动时item下方的组件(List水平布局时)。* - edgeEffect: 滑动效果。*/.swipeAction({end: this.DeleteTaskButton(index)})})}.width("100%").layoutWeight(1) //忽略元素本身尺寸设置,表示自适应占满剩余空间。.alignListItem(ListItemAlign.Center) //ListItem在List交叉轴方向的布局方式(这里就是水平方向居中对齐),默认为首部对齐。}.width("100%").height("100%")}
}

五、@Provide和Consume

@Provide和Consume可以跨组件提供类似于@State和@Link的双向同步。如下图所示:

但是需要注意 :

  • @Provide:父组件使用
  • @Consume:子组件或者后代组件使用
  • 同时在在调用子组件或者后代组件的时候,子组件或者后代组件定义了参数,也是不需要传入,会自动隐式的传入

代码案例如下:

//任务类
class Task{static  id:number = 1;//任务名称,id每次增加1name:string = `任务${Task.id++}`//任务状态,是否完成taskStatus:boolean = false
}//统一的卡片样式
@Styles function  card(){.width("90%").padding(20).backgroundColor(Color.White).borderRadius(15)//为当前组件添加阴影效果.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}//将统计信息抽取出来形成一个类
class StateInfo{//总任务数量totalTask:number//已完成数量finishTask:numberconstructor( totalTask:number = 0,finishTask:number = 0 ) {this.totalTask = totalTaskthis.finishTask = finishTask}
}@Entry
@Component
struct StatusManagement {//TODO 父子组件变量类型是对象, @Prop子组件变量类型是对象的属性//创建统计信息对象@Provide stat: StateInfo = new StateInfo()build() {Column({space:20}){//1.任务进度 这里直接调用自定义的组件,使用的是@Prop,通过属性传入TaskStatusProgress()//2.任务列表//TODO 子组件使用的@Link, 通过$符的方式传值TaskList()}.size({width:"100%",height:"100%"}).backgroundColor("#F0F8FF")}
}/*** 定义任务进度组件* 使用@Prop装饰器,监控父组件的数据状态,而改变自身的数据*/
@Component
struct TaskStatusProgress {//TODO 通过@Consume实现双向同步,调用组件的时候不需要传入值,会自动传入@Consume stat: StateInfobuild() {//1.任务进度Row(){Text("任务进度:").fontSize(30) //字体大小.fontWeight(FontWeight.Bold)//字体加粗//环形和数字要使用堆叠容器,Stack(){//环形组件: 进度、总量、样式Progress({value:this.stat.finishTask, total:this.stat.totalTask,type:ProgressType.Ring}).width(90)Row(){//让数字显示在一起,放在一个容器中//任务完成量Text(`${this.stat.finishTask}`).fontSize(25) //字体大小.fontColor("#0000CD")//任务总量Text(` / ${this.stat.totalTask}`).fontSize(25) //字体大小}}}.width("100%").margin({top:20,bottom:20}).justifyContent(FlexAlign.SpaceAround) //主轴方向布局.card()}
}/*** 定义任务列表子组件*/
@Component
struct TaskList {//TODO 通过@Consume实现双向同步,调用组件的时候不需要传入值,会自动传入@Consume stat: StateInfo//保存添加任务的数组@State tasks: Task[] = []//将跟新数据的操作进一步抽取DataUpdate(){//需要跟新一下任务总量(就是任务数组的长度)this.stat.totalTask = this.tasks.length//跟新已完成任务总数this.stat.finishTask = this.tasks.filter(item=> item.taskStatus).length}//自定义删除删除@Builder DeleteTaskButton(index:number){Button(){Image($r("app.media.icon_remove_button")).width(20).fillColor("#B0E0E6")}.width(40).height(40).type(ButtonType.Circle).onClick(()=>{//去数组中删除this.tasks.splice(index, 1)//上面的更新数据进一步封装,然后调用this.DataUpdate()}).backgroundColor(Color.Red).margin(10)}build() {Column(){//2.添加任务按钮Button("添加任务").width(200).onClick(()=>{//1.添加任务,就是给任务数组中添加一个值this.tasks.push(new Task())//2.新增任务后,需要跟新一下任务总量(就是任务数组的长度)this.stat.totalTask = this.tasks.length})//3.任务列表List({space:5}){ForEach(this.tasks,(item:Task, index:number)=>{ListItem(){Row(){//文本Text(item.name).fontColor(20)//单选框,select决定是否选中,类型布尔值,取Task对象属性taskStatusCheckbox().select(item.taskStatus).onChange((value:boolean)=>{//1.更新当前已完成任务状态,勾选后修改状态为trueitem.taskStatus = value/*//2.统计已完成的数量,就是统计数组中状态为true的元素个数this.finishTask = this.tasks.filter(item=> item.taskStatus).length*///上面的更新数据进一步封装,然后调用this.DataUpdate()})}.width("100%").card().justifyContent(FlexAlign.SpaceBetween)}/*** 用于设置ListItem的划出组件。* - start: ListItem向右划动时item左边的组件(List垂直布局时)或ListItem向下划动时item上方的组件(List水平布局时)。* - end: ListItem向左划动时item右边的组件(List垂直布局时)或ListItem向上划动时item下方的组件(List水平布局时)。* - edgeEffect: 滑动效果。*/.swipeAction({end: this.DeleteTaskButton(index)})})}.width("100%").layoutWeight(1) //忽略元素本身尺寸设置,表示自适应占满剩余空间。.alignListItem(ListItemAlign.Center) //ListItem在List交叉轴方向的布局方式(这里就是水平方向居中对齐),默认为首部对齐。}.width("100%").height("100%")}
}

预览效果如下:

六、@Observed和@objectLink

@objectLink和@observed装饰器用于在涉及嵌套对象或数组元素为对象的场景中进行双向数据同步

6.1.案例1

以之前的学生信息展示的基础案例中,点击修改学生宠物年龄的功能和修改宠物列表中宠物信息,修改后无法同步为例,原因在于:

  • 学生的宠物年龄,是属于对象的嵌套
  • 宠物列表是属于数组中有对象

要解决上面的问题,就需要@Observed和@objectLink装饰器来实现

1)需要给嵌套的对象和数组中对象添加@Observed装饰器,Pet对象属于嵌套的所以添加装饰器

class Student{sid:numbername:stringage:number//宠物pet:Petconstructor(sid:number,name:string,age:number,pet:Pet) {this.sid = sidthis.name = namethis.age = agethis.pet = pet}
}@Observed //实现双向数据同步
//宠物
class Pet{petName:stringpetAge:numberconstructor(petName:string,petAge:number) {this.petName = petNamethis.petAge = petAge}
}

2)将需要修改重新渲染的功能抽取出来定义子组件,然后给变量添加@objectLink注解

/*** 数组元素为对象,实现数据同步*/
@Component
struct PetList {//子组件的变量必须使用@ObjectLink@ObjectLink pet:Petbuild() {Row(){Text(`${this.pet.petName}:${this.pet.petAge}`).fontSize(20)Button("修改年龄").onClick(()=>{//点击后发现修改了数据,但是由于属性属于数组的对象,@State无法让修改后自动渲染this.pet.petAge++})}.width("100%").justifyContent(FlexAlign.SpaceAround)}
}/*** 嵌套对象,实现数据同步*/
@Component
struct PetInfo {//子组件的变量必须使用@ObjectLink@ObjectLink pet:Petbuild() {//修改Student的属性是可以的Text(`宠物:${this.pet.petName},${this.pet.petAge}`).fontSize(30)}
}

注意:其中的对象嵌套,学生对象里面有个宠物对象,这里在定义的时候,接受的参数一定是宠物对象

3)调用定义的子组件

@Entry
@Component
struct StateExample03{//私有变量的值是一个对象@State s:Student = new Student(2301,"马保国", 73, new Pet("大黄",3))//准备一个数组@State pets:Pet[] = [new Pet("小白",2300), new Pet("小痴", 1100)]build() {Column({space:20}){/*** 数组元素为对象,实现数据同步* 调用PetInfo, 这里的this.s.pet是属于student对象的pet属性*/PetInfo({pet:this.s.pet}).onClick(()=>{//变量通过@State修饰,点击修改私有变量(点击一次自增1),然后会自动修改刷新UIthis.s.pet.petAge++})//添加宠物Button("添加").onClick(()=>{this.pets.push(new Pet("小灰"+1, 10))})Text("---------宠物列表------").fontSize(30).width("100%")ForEach(this.pets,(pet:Pet, index)=>{/*** 嵌套对象,实现数据同步* 调用PetList*/PetList({pet:pet}).onClick(()=>{//变量通过@State修饰,点击修改私有变量(点击一次自增1),然后会自动修改刷新UIthis.s.pet.petAge++})})}.width("100%").height("100%").justifyContent(FlexAlign.Center)//主轴方向对齐}
}
6.1.案例2

还是任务进度列表案例,之前的功能还剩余一部分,当任务完成后,任务的名称需要置灰并且出现中划线,效果如下所示:

1)在任务类上添加装饰器@Observed

//任务类
@Observed
class Task{static  id:number = 1;//任务名称,id每次增加1name:string = `任务${Task.id++}`//任务状态,是否完成taskStatus:boolean = false
}

2)在任务列表中渲染任务组件功能抽取出来形成子组件,里面使用@ObjectLink装饰器修饰变量

//任务列表置灰加下划线样式组件
@Extend(Text) function finishedTask(){.decoration({type:TextDecorationType.LineThrough}) //LineThrough.fontColor("#B1B2B1")
}/*** 这个由于任务列表里面存放的对象,所以需要使用@objectLink,实现双向同步,抽取组件*/
@Component
struct TaskItem {//双向同步数组中的对象@ObjectLink item:Task//由于数据更新函数,在父组件TaskList,无法移动到这里,所以需要把父组件中的数据跟新的函数DataUpdate(),当成参数传递给子组件onChangeTask: ()=>void //表示onChangeTask是一个无参返回值为void的函数build() {Row(){//TODO 判断是否是完成状态,如果是完成状态,则修改为置灰加中划线if(this.item.taskStatus){Text(this.item.name).finishedTask() //调用定义的样式组件}else {//文本Text(this.item.name).fontColor(20)}//单选框,select决定是否选中,类型布尔值,取Task对象属性taskStatusCheckbox().select(this.item.taskStatus).onChange((value:boolean)=>{//1.更新当前已完成任务状态,勾选后修改状态为truethis.item.taskStatus = value//2.上面的更新数据进一步封装,然后调用this.onChangeTask() //更新数据方法在父组件,当成参数传递到这里,然后调用})}.width("100%").card().justifyContent(FlexAlign.SpaceBetween)}
}

3)在任务列表组件中调用上面封装的子组件 TaskItem,代码如下:

/*** 定义任务列表子组件*/
@Component
struct TaskList {//TODO 通过@Consume实现双向同步,调用组件的时候不需要传入值,会自动传入@Consume stat: StateInfo//保存添加任务的数组@State tasks: Task[] = []//将跟新数据的操作进一步抽取DataUpdate(){//需要跟新一下任务总量(就是任务数组的长度)this.stat.totalTask = this.tasks.length//跟新已完成任务总数this.stat.finishTask = this.tasks.filter(item=> item.taskStatus).length}//自定义删除删除@Builder DeleteTaskButton(index:number){Button(){Image($r("app.media.icon_remove_button")).width(20).fillColor("#B0E0E6")}.width(40).height(40).type(ButtonType.Circle).onClick(()=>{//去数组中删除this.tasks.splice(index, 1)//上面的更新数据进一步封装,然后调用this.DataUpdate()}).backgroundColor(Color.Red).margin(10)}build() {Column(){//2.添加任务按钮Button("添加任务").width(200).onClick(()=>{//1.添加任务,就是给任务数组中添加一个值this.tasks.push(new Task())//2.新增任务后,需要跟新一下任务总量(就是任务数组的长度)this.stat.totalTask = this.tasks.length})//3.任务列表List({space:5}){ForEach(this.tasks,(item:Task, index:number)=>{ListItem(){//实现数组中对象数据的同步,调用封装的子组件//this.DataUpdate.bind(this)将函数当成参数传递过去,bind(this)表示使用父组件TaskList的对象,因为更新的数据在父组件TaskList中TaskItem({item:item, onChangeTask:this.DataUpdate.bind(this)})}/*** 用于设置ListItem的划出组件。* - start: ListItem向右划动时item左边的组件(List垂直布局时)或ListItem向下划动时item上方的组件(List水平布局时)。* - end: ListItem向左划动时item右边的组件(List垂直布局时)或ListItem向上划动时item下方的组件(List水平布局时)。* - edgeEffect: 滑动效果。*/.swipeAction({end: this.DeleteTaskButton(index)})})}.width("100%").layoutWeight(1) //忽略元素本身尺寸设置,表示自适应占满剩余空间。.alignListItem(ListItemAlign.Center) //ListItem在List交叉轴方向的布局方式(这里就是水平方向居中对齐),默认为首部对齐。}.width("100%").height("100%")}
}

这里有个新的问题,新定义的子组件TaskItem中没有数据更新的方法DataUpdate,这时候无法更新数据,而更新数据的方法在TaskList中,为了能在子组件中调用父组件的函数,就需要在组件中定义一个参数为函数,调用的时候把数据更新方法当做函数传入即可,语法如下:

调用的时候,数据更新的方法DataUpdate,更新的数据也在父组件中,所以需要指定是修改的父组件中的数据(绑定父组件的this),如下:

4)完整的代码如下:

//任务类
@Observed
class Task{static  id:number = 1;//任务名称,id每次增加1name:string = `任务${Task.id++}`//任务状态,是否完成taskStatus:boolean = false
}//统一的卡片样式
@Styles function  card(){.width("90%").padding(20).backgroundColor(Color.White).borderRadius(15)//为当前组件添加阴影效果.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}//将统计信息抽取出来形成一个类
class StateInfo{//总任务数量totalTask:number//已完成数量finishTask:numberconstructor( totalTask:number = 0,finishTask:number = 0 ) {this.totalTask = totalTaskthis.finishTask = finishTask}
}@Entry
@Component
struct StatusManagement {//TODO 父子组件变量类型是对象, @Prop子组件变量类型是对象的属性//创建统计信息对象@Provide stat: StateInfo = new StateInfo()build() {Column({space:20}){//1.任务进度 这里直接调用自定义的组件,使用的是@Prop,通过属性传入TaskStatusProgress()//2.任务列表//TODO 子组件使用的@Link, 通过$符的方式传值TaskList()}.size({width:"100%",height:"100%"}).backgroundColor("#F0F8FF")}
}/*** 定义任务进度组件* 使用@Prop装饰器,监控父组件的数据状态,而改变自身的数据*/
@Component
struct TaskStatusProgress {//TODO 通过@Consume实现双向同步,调用组件的时候不需要传入值,会自动传入@Consume stat: StateInfobuild() {//1.任务进度Row(){Text("任务进度:").fontSize(30) //字体大小.fontWeight(FontWeight.Bold)//字体加粗//环形和数字要使用堆叠容器,Stack(){//环形组件: 进度、总量、样式Progress({value:this.stat.finishTask, total:this.stat.totalTask,type:ProgressType.Ring}).width(90)Row(){//让数字显示在一起,放在一个容器中//任务完成量Text(`${this.stat.finishTask}`).fontSize(25) //字体大小.fontColor("#0000CD")//任务总量Text(` / ${this.stat.totalTask}`).fontSize(25) //字体大小}}}.width("100%").margin({top:20,bottom:20}).justifyContent(FlexAlign.SpaceAround) //主轴方向布局.card()}
}/*** 定义任务列表子组件*/
@Component
struct TaskList {//TODO 通过@Consume实现双向同步,调用组件的时候不需要传入值,会自动传入@Consume stat: StateInfo//保存添加任务的数组@State tasks: Task[] = []//将跟新数据的操作进一步抽取DataUpdate(){//需要跟新一下任务总量(就是任务数组的长度)this.stat.totalTask = this.tasks.length//跟新已完成任务总数this.stat.finishTask = this.tasks.filter(item=> item.taskStatus).length}//自定义删除删除@Builder DeleteTaskButton(index:number){Button(){Image($r("app.media.icon_remove_button")).width(20).fillColor("#B0E0E6")}.width(40).height(40).type(ButtonType.Circle).onClick(()=>{//去数组中删除this.tasks.splice(index, 1)//上面的更新数据进一步封装,然后调用this.DataUpdate()}).backgroundColor(Color.Red).margin(10)}build() {Column(){//2.添加任务按钮Button("添加任务").width(200).onClick(()=>{//1.添加任务,就是给任务数组中添加一个值this.tasks.push(new Task())//2.新增任务后,需要跟新一下任务总量(就是任务数组的长度)this.stat.totalTask = this.tasks.length})//3.任务列表List({space:5}){ForEach(this.tasks,(item:Task, index:number)=>{ListItem(){//实现数组中对象数据的同步,调用封装的子组件//this.DataUpdate.bind(this)将函数当成参数传递过去,bind(this)表示使用父组件TaskList的对象,因为更新的数据在父组件TaskList中TaskItem({item:item, onChangeTask:this.DataUpdate.bind(this)})}/*** 用于设置ListItem的划出组件。* - start: ListItem向右划动时item左边的组件(List垂直布局时)或ListItem向下划动时item上方的组件(List水平布局时)。* - end: ListItem向左划动时item右边的组件(List垂直布局时)或ListItem向上划动时item下方的组件(List水平布局时)。* - edgeEffect: 滑动效果。*/.swipeAction({end: this.DeleteTaskButton(index)})})}.width("100%").layoutWeight(1) //忽略元素本身尺寸设置,表示自适应占满剩余空间。.alignListItem(ListItemAlign.Center) //ListItem在List交叉轴方向的布局方式(这里就是水平方向居中对齐),默认为首部对齐。}.width("100%").height("100%")}
}//任务列表置灰加下划线样式组件
@Extend(Text) function finishedTask(){.decoration({type:TextDecorationType.LineThrough}) //LineThrough.fontColor("#B1B2B1")
}/*** 这个由于任务列表里面存放的对象,所以需要使用@objectLink,实现双向同步,抽取组件*/
@Component
struct TaskItem {//双向同步数组中的对象@ObjectLink item:Task//由于数据更新函数,在父组件TaskList,无法移动到这里,所以需要把父组件中的数据跟新的函数DataUpdate(),当成参数传递给子组件onChangeTask: ()=>void //表示onChangeTask是一个无参返回值为void的函数build() {Row(){//TODO 判断是否是完成状态,如果是完成状态,则修改为置灰加中划线if(this.item.taskStatus){Text(this.item.name).finishedTask() //调用定义的样式组件}else {//文本Text(this.item.name).fontColor(20)}//单选框,select决定是否选中,类型布尔值,取Task对象属性taskStatusCheckbox().select(this.item.taskStatus).onChange((value:boolean)=>{//1.更新当前已完成任务状态,勾选后修改状态为truethis.item.taskStatus = value//2.上面的更新数据进一步封装,然后调用this.onChangeTask() //更新数据方法在父组件,当成参数传递到这里,然后调用})}.width("100%").card().justifyContent(FlexAlign.SpaceBetween)}
}

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线[包含了大APP实战项目开发]。

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

第二阶段:鸿蒙南北双向高工技能基础:https://qr21.cn/Bm8gyp

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:https://qr21.cn/Bm8gyp

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:https://qr21.cn/Bm8gyp

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):https://qr21.cn/Bm8gyp

鸿蒙入门教学视频:

美团APP实战开发教学:https://qr21.cn/Bm8gyp

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:https://qr21.cn/FV7h05


http://www.mrgr.cn/p/13465551

相关文章

计算机Windows系统优化小知识

本文涉及计算机Windows系统优化小知识,介绍了注册表、虚拟内存、常用优化工具目录目录什么是注册表优化优化工具什么是注册表注册表是保存所有系统设置数据的存储器。注册表保存了 Windows 运行所需的各种参数和设置,以及应用程序相关的所有信息。从 Windows启动开始,到用户…

CUDA和CUDNN版本切换

介绍了cuda和cudnn版本切换的方法,以及设置环境变量的坑0 背景 在用不同框架做深度学习时,难免会遇到需要不同版本的cuda和cudnn版本的情况,如果把原来版本的卸载掉重新安装新版本,则会影响其它框架的使用,最好的方法是在主机上安装多个版本的cuda和cudnn,需要用到哪种就…

用Python将原始边列表转换为邻接矩阵

👽发现宝藏 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在图论和网络分析中,图是一种非常重要的数据结构,它由节点&#xff…

计算机DIY之接驳线缆

介绍计算机DIY过程中接驳线缆相关知识,CPU供电、主板主供电、显卡供电、SATA供电、大4pin供电、主板接驳、前面板接驳目录目录 接驳线缆 CPU供电: 主板主供电 显卡供电 SATA供电 大4pin供电 主板接驳 前面板接驳接驳线缆电源插头里还有3条ATX电源专有的线,一条绿色线…

【继承和多态】

闭上眼睛,什么都不听.............................................................................................................. 文章目录 前言 一、【继承】 1.1【继承的概念】 1.2【 继承的定义】 1.2.1【定义格式】 1.2.2【继承关系和访问限定符】 1.2…

硬盘保存及维护基本常识

介绍硬盘使用寿命、硬盘供电、硬盘保存相关小知识点目录目录 硬盘使用寿命简介 硬盘供电简介 硬盘保存简介硬盘使用寿命简介硬盘在连续使用3-4年后就需要注意了(一般为质保期时间后一点), 5-6年后就需要更换硬盘了. 五年左右的时候留意更换机械硬盘,如果不是特备重要的数据,可…

使用restful请求华三模拟器上的设备接口数据

一、resful介绍 RESTful采用C/S模型。RESTful客户端为使用Python、Ruby或Java等编程语言开发出的RESTful客户端程序或脚本。RESTful服务器为网络设备。通过RESTful功能配置和维护设备的过程为: (1) 客户端向服务器发送HTTP/HTTPS请求报文,通过HTTP的方法来操作指定的REST…

芯科SiWx917学习笔记:1-测试Out of Box Demo

实验目的:测试Out of Box Demo 实验环境:Simplicity Studio V5 实验器材:Wireless Starter Kit Mainboard (BRD4002A Rev A06) + SiWG917 Single Band Wi-Fi and BLE 8MB Flash Radio Board (BRD4338A Rev A01) 实验开始: 1. 新建工程:在demos中找到Out of Box Demo(SoC) …

HTML批量文件上传方案——图像预览方式

作者:私语茶馆 1.HTML多文件上传的关键方案 多文件上传包括:文件有效性校验,文件预览、存储和进度展示多个方面,本章节介绍的是文件预览的实现方案。 2.文件上传前预览 2.1.效果 选择文件前: 选择文件后: 2.2.CSS文件代码 StorageCenter.css代码 html {font-family:…

牛客NC371 验证回文字符串(二)【简单 双指针 C++/Java/Go/PHP】

题目 题目链接: https://www.nowcoder.com/practice/130e1a9eb88942239b66e53ec6e53f51 思路 直接看答案,不难参考答案C class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可…

合合信息:acge_text_embedding 文本向量化模型登顶 C-MTEB 中文榜单

近期,合合信息的 acge_text_embedding 文本向量化模型在最近的比赛中获得了 MTEB 中文榜单(C-MTEB)榜首!C-MTEB 作为中文文本向量性能的评测标准,以其全面性和权威性在业内享有盛誉值得关注。接下来让我们仔细分析一下…

PID的嵌入式应用

PID 适用范围:二阶以内线性系统。 高阶系统并不适用,但是可以化为二阶系统。 非线性系统也不适用,可以通过其他方式化为线性系统。 优势:应用范围 95% ,不需要对系统进行精细化建模,可以直接上 PID 。…

pwn知识——劫持tcache_perthread_struct(Ubuntu22.04之前)

前言(可忽略) 堆不愧是堆...知识点真的要多用动调查看堆的状态才好理解 tcache_perthread_struct的结构 源码 #define TCACHE_MAX_BINS 64 /* We overlay this structure on the user-data portion of a chunk whenthe chunk is stored in the per-thread cache. */ typedef…

最强AI直播换脸软件,DeepFaceLive下载介绍

DeepFaceLive是一款专注于直播实时换脸的AI软件,使用经过长时间训练的人脸模型替换摄像头中的人脸,能够产生接近电影质量的面部合成效果,提供高保真的视觉体验,在新版本中也支持了图片换脸(视频换脸只能预览,不能保存) DeepFaceLive在直播场景下的效果高度逼真,强大的…

stable-diffusion-webui安装与使用过程中的遇到的error合集

stable-diffusion-webui1.9.2踩坑安装 1. 安装过程1.1 stable-diffusion-webui1.2 在win11或win10系统安装,需修改两个启动脚本1.2.1 修改webui-user.bat1.2.2 修改webui.bat 1.3 双击 webui-user.bat 启动脚本1.3.1 no module xformers. Processing without on fre…

rabbitmq系列03---发布确认

一、发布确认逻辑 生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID (从 1 开始),一旦消息被投递到所有匹配的队列之后,broker 就会发送一个确认给生产者 (包含消息的唯一 ID),这就使得生产者知道消息已经…

生成式AI原理技术详解(一)——神经网络与深度学习

本文主要介绍了生成式AI的最新发展,提到了GPT-5和AI软件工程师在行业中的影响,指出AI技术进步对国家竞争和个人职业发展的潜在影响。 未来已来 最近有两则新闻: sam altman自曝GPT-5细节,公开宣称GPT-5提升将非常大,任…

玩转手机在AidLux上安装宝塔面板

AidLux,手机不用刷机、不用root,直接在手机应用市场就能下载使用。 1.4G的应用包,看起来挺大的,那是因为内嵌了一套完整的AIoT应用开发和部署平台。 不仅Android手机可以玩,华为的Harmony系统也可以使用。 使用它最主…

关于ida f5时报错lumina无法连接到云服务器的问题

在用ida的时候不知道怎么回事突然就f5不了了,报错 Decompilation failure: 4005F7: cloud: Server is not available Please refer to the manual to find appropriate actionslumina: connect: 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。4005F…

在Elasticsearch 7.9.2中安装IK分词器并进行自定义词典配置

Elasticsearch是一个强大的开源搜索引擎,而IK分词器是针对中文文本分析的重要插件。本文将引导您完成在Elasticsearch 7.9.2版本中安装IK分词器、配置自定义词典以及验证分词效果的全过程。 步骤一:下载IK分词器 访问IK分词器的GitHub发布页面&#xf…