在父子组件需要进行数据同步的时候,可以通过@Prop和@Link装饰器来做到。在父组件中用@State装饰,在自组件中用@Prop或@Link装饰。
结论:@Prop用于子组件只监听父组件的数据改变而改变,自己不对数据改变
@Link用于子组件与父组件都会对数据改变,都需要在数据改变的时候发生相应的更新。
@Prop的数据是单向传递的,父组件改变能通知子组件,但是子组件改变不能通知父组件。
@Link的数据是双向传递的,父组件改变能通知子组件,子组件的改变也可以通知父组件。
@Prop可以装饰的类型有限
@Link可以装饰的类型较多
@Prop装饰的属性不能初始化,因为传递方式是拷贝,从父组件中拷贝一份值给子组件。所以可以直接通过this.xxx传递。
@Link装饰的属性也不能初始化。传递方式是指针传递,需要用$修饰传递的属性。
接上一篇state案例,我们在同一个组件中编写的代码,导致代码非常臃肿。那么,为了更加简洁,就对代码进行了拆分,将代码又拆分出来了2个组件TaskStitics(任务进度卡片)和StaticList(任务列表卡片)。
其中,TaskStitics组件只接收数据的改变,不会对数据改变,所以采用单向同步的@Prop装饰
StaticList组件因为可以对数据进行完成、删除等操作,也会对父组件的数据改变,所以采用双向同步的@Link装饰。
class Task {
static id: number = 1
name:string = '任务名称'+Task.id++
finished:boolean = false
}
@Styles function card() {
.width('90%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({radius:6,color:'#1F00000',offsetX:2,offsetY:4})
}
@Extend(Text) function tasksuccessed(finish: boolean) {
.decoration({type: finish ? TextDecorationType.LineThrough : TextDecorationType.None})
.fontColor(finish? '#B1B2B1': Color.Black)
}
@Entry
@Component
struct ProgressTask {
@State totalTasks: number = 0
@State finishTasks:number = 0
@State tasks: Task[] = []
build() {
Column() {
//顶部任务进度卡片
TaskStitics({ totalTasks: this.totalTasks, finishTasks: this.finishTasks })
//任务列表+新增按钮
StaticList({totalTasks:$totalTasks,finishTasks:$finishTasks,tasks:$tasks})
}
}
}
@Component
struct TaskStitics {
@Prop totalTasks: number
@Prop finishTasks:number
build() {
//进度卡片
Row() {
Text('任务进度:')
.fontWeight(FontWeight.Bold)
.fontSize(30)
.layoutWeight(1)
Stack() {
Progress({value:this.finishTasks,total:this.totalTasks,type:ProgressType.Ring})
.width(100)
Row() {
Text(this.finishTasks.toString())
.fontSize(24)
.fontColor('#36D')
Text(' / '+this.totalTasks.toString())
.fontSize(24)
}
}
}
.card()
.height(150)
.margin({top:20,bottom:10})
.justifyContent(FlexAlign.SpaceEvenly)
}
}
@Component
struct StaticList {
@Link totalTasks: number
@Link finishTasks:number
@Link tasks: Task[]
handleTaskNumber() {
this.totalTasks = this.tasks.length
this.finishTasks = this.tasks.filter(item =>
item.finished
).length
console.log('完成任务数'+this.finishTasks)
}
@Builder deleteButton(index:number) {
Button('➖')
.fontColor(Color.White)
.backgroundColor(Color.Red)
.width(40)
.height(40)
.type(ButtonType.Circle)
.margin({left:5})
.onClick(() => {
this.tasks.splice(index,1)
this.handleTaskNumber()
})
}
build() {
Column() {
//添加按钮
Button('新增任务')
.width(200)
.height(35)
.onClick(() => {
this.tasks.push(new Task())
this.handleTaskNumber()
})
.margin({ bottom: 20 })
//任务列表
List({ space: 10 }) {
ForEach(this.tasks, (task: Task, index) => {
ListItem() {
Row() {
Text(task.name)
.fontSize(20)
.tasksuccessed(task.finished)
Checkbox()
.select(task.finished)
.onChange(value => {
task.finished = value
console.log('任务状态' + value + '')
this.handleTaskNumber()
})
}
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
.swipeAction({ end: this.deleteButton(index) })
}, item => '' + item.name)
}
.width('100%')
.alignListItem(ListItemAlign.Center)
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
@Provide和@Consume可以跨组件提供类似于@State和@Link的双向同步。
设想有三级组件父组件->子组件->孙子组件。
如果想要父组件与孙子组件中的数据达到双向同步,那么就需要先父组件与子组件绑定,然后子组件与孙子组件绑定,需要绑定多次。这个时候就可以用@Provide和@Consume装饰器了。
在父组件中用@Provide装饰,在孙子组件中采用@Consume装饰,就可以实现双向数据同步。
需要注意的是,使用这两个装饰器的时候不需要在父组件中传递参数。
整体关系如下图