@Prop 和 @Link
当父子组件之间需要数据同步时,可以使用@Prop和@Link装饰器。
@Prop | @Link | |
---|---|---|
同步类型 | 单向同步 | 双向同步 |
允许装饰的变量类型 | @Prop只支持string、number、boolean、enum类型 ,父组件对象类型,自组件对象属性不可以是数组、any | 父子类型一致:string、number、boolean,enum,object,class以及他们的数组。数组中元素增、删、替换会引起刷新。嵌套类型以及数组中的对象属性无法触发页面更新 |
初始化方式 | 不允许子组件初始化 | 父组件传递,禁止子组件初始化 |
代码示例
class Task {
static id: number = 1
name: string = '任务' + Task.id++
isDone: boolean = false
}
@Extend(Text) function finishedTask() {
.decoration({ type: TextDecorationType.LineThrough })
.fontColor('#B1B2B1')
}
@Styles function commonCardStyle() {
.width('95%')
.margin({ left: 10, right: 10, top: 10 })
.borderRadius(20)
.backgroundColor('#ffffff')
.shadow({ radius: 6, color: '#1f000000', offsetX: 2, offsetY: 4 })
}
@Entry
@Component
struct TaskPage {
// 总任务数量
@State totalTask: number = 0
@State finishTask: number = 0
@State taskList: Array<Task> = []
build() {
Column() {
// 1.顶部任务统计部分
TaskStatisticsView({ totalTask: this.totalTask, finishTask: this.finishTask })
// 2.任务列表 @Link 因为传递的是引用,必须使用$
TaskListView({ totalTask: $totalTask, finishTask: $finishTask, taskList: $taskList })
}
.width('100%')
.height('100%')
.backgroundColor('#eeeeee')
.justifyContent(FlexAlign.Start)
}
}
@Component
struct TaskStatisticsView {
// @Prop:禁止初始化,单向变更,父组件变动,引起子组件刷新
@Prop totalTask: number
@Prop finishTask: number
build() {
Row() {
Text("任务进度:")
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ right: 40 })
Stack() {
Progress({ value: this.finishTask, type: ProgressType.Ring, total: this.totalTask }).width(120)
Text(this.finishTask + "/" + this.totalTask)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
}.commonCardStyle()
.height(200)
.padding({ left: 20, right: 20 })
.justifyContent(FlexAlign.Center)
}
}
@Component
struct TaskListView {
@Link totalTask: number
@Link finishTask: number
@Link taskList: Array<Task>
build() {
Column() {
// 2.新增任务
Button("新增任务").onClick(() => {
this.taskList.push(new Task())
this.totalTask = this.taskList.length
}).width("60%")
.margin({ top: 10 })
// 3.任务列表
List() {
ForEach(this.taskList, (item, index) => {
ListItem() {
this.TaskItemView(item)
}.swipeAction({ end: this.getDeleteButton(index) }) // 向左滑动,出现删除按钮
})
}.layoutWeight(1) // 高度权重
.width('100%')
.alignListItem(ListItemAlign.Center)
}
}
@Builder TaskItemView(task: Task) {
Row() {
// 这里不生效,原因:state 数组对象嵌套不刷新视图
if (task.isDone) {
Text(task.name)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.margin({ right: 40 })
.finishedTask()
} else {
Text(task.name)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.margin({ right: 40 })
}
Checkbox()
.select(task.isDone).onChange((isChecked) => {
task.isDone = isChecked
this.finishTask = this.taskList.filter(item => item.isDone).length
})
}.commonCardStyle()
.height(100)
.padding({ left: 20, right: 20 })
.justifyContent(FlexAlign.SpaceBetween)
}
@Builder getDeleteButton(index: number) {
Button({ type: ButtonType.Circle }) {
Image($r('app.media.del'))
}
.onClick(() => {
this.taskList.splice(index, 1)
this.finishTask = this.taskList.filter(item => item.isDone).length
this.totalTask = this.taskList.length
})
.width(50)
.height(50)
.padding(10)
.margin({ right: 5 })
.backgroundColor('#ffffff')
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
将totalTask与finishedTask封装为对象依旧生效
class Task {
static id: number = 1
name: string = '任务' + Task.id++
isDone: boolean = false
}
@Extend(Text) function finishedTask() {
.decoration({ type: TextDecorationType.LineThrough })
.fontColor('#B1B2B1')
}
@Styles function commonCardStyle() {
.width('95%')
.margin({ left: 10, right: 10, top: 10 })
.borderRadius(20)
.backgroundColor('#ffffff')
.shadow({ radius: 6, color: '#1f000000', offsetX: 2, offsetY: 4 })
}
// 封装为对象
class StatInfo {
totalTask: number = 0
finishTask: number = 0
}
@Entry
@Component
struct TaskPage {
@State stat: StatInfo = new StatInfo()
build() {
Column() {
// 1.顶部任务统计部分
TaskStatisticsView({ totalTask: this.stat.totalTask, finishTask: this.stat.finishTask })
// 2.任务列表 @Link 因为传递的是引用,必须使用$
TaskListView({ stat: $stat })
}
.width('100%')
.height('100%')
.backgroundColor('#eeeeee')
.justifyContent(FlexAlign.Start)
}
}
@Component
struct TaskStatisticsView {
// @Prop:禁止初始化,单向变更,父组件变动,引起子组件刷新
@Prop totalTask: number
@Prop finishTask: number
build() {
Row() {
Text("任务进度:")
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ right: 40 })
Stack() {
Progress({ value: this.finishTask, type: ProgressType.Ring, total: this.totalTask }).width(120)
Text(this.finishTask + "/" + this.totalTask)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
}.commonCardStyle()
.height(200)
.padding({ left: 20, right: 20 })
.justifyContent(FlexAlign.Center)
}
}
@Component
struct TaskListView {
@Link stat: StatInfo
@State taskList: Array<Task> = []
handleStatistics() {
this.stat.finishTask = this.taskList.filter(item => item.isDone).length
this.stat.totalTask = this.taskList.length
}
build() {
Column() {
// 2.新增任务
Button("新增任务").onClick(() => {
this.taskList.push(new Task())
this.stat.totalTask = this.taskList.length
}).width("60%")
.margin({ top: 10 })
// 3.任务列表
List() {
ForEach(this.taskList, (item, index) => {
ListItem() {
this.TaskItemView(item)
}.swipeAction({ end: this.getDeleteButton(index) }) // 向左滑动,出现删除按钮
})
}.layoutWeight(1) // 高度权重
.width('100%')
.alignListItem(ListItemAlign.Center)
}
}
@Builder TaskItemView(task: Task) {
Row() {
// 这里不生效,原因:state 数组对象嵌套不刷新视图
if (task.isDone) {
Text(task.name)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.margin({ right: 40 })
.finishedTask()
} else {
Text(task.name)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.margin({ right: 40 })
}
Checkbox()
.select(task.isDone).onChange((isChecked) => {
task.isDone = isChecked
this.handleStatistics()
})
}.commonCardStyle()
.height(100)
.padding({ left: 20, right: 20 })
.justifyContent(FlexAlign.SpaceBetween)
}
@Builder getDeleteButton(index: number) {
Button({ type: ButtonType.Circle }) {
Image($r('app.media.del'))
}
.onClick(() => {
this.taskList.splice(index, 1)
this.handleStatistics()
})
.width(50)
.height(50)
.padding(10)
.margin({ right: 5 })
.backgroundColor('#ffffff')
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
@Provide和@Consume
@Provide和Consume可以跨组件提供类似@State和@Link的双向同步
代码示例
class Task {
static id: number = 1
name: string = '任务' + Task.id++
isDone: boolean = false
}
@Extend(Text) function finishedTask() {
.decoration({ type: TextDecorationType.LineThrough })
.fontColor('#B1B2B1')
}
@Styles function commonCardStyle() {
.width('95%')
.margin({ left: 10, right: 10, top: 10 })
.borderRadius(20)
.backgroundColor('#ffffff')
.shadow({ radius: 6, color: '#1f000000', offsetX: 2, offsetY: 4 })
}
class StatInfo {
totalTask: number = 0
finishTask: number = 0
}
@Entry
@Component
struct TaskPage {
// @Provide 跨组件传递且被@Provide装饰器修饰的组件不需要被传递
@Provide stat: StatInfo = new StatInfo()
build() {
Column() {
// 1.顶部任务统计部分
TaskStatisticsView()
// 2.任务列表 @Link 因为传递的是引用,必须使用$
TaskListView()
}
.width('100%')
.height('100%')
.backgroundColor('#eeeeee')
.justifyContent(FlexAlign.Start)
}
}
@Component
struct TaskStatisticsView {
@Consume stat: StatInfo
build() {
Row() {
Text("任务进度:")
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ right: 40 })
Stack() {
Progress({ value: this.stat.finishTask, type: ProgressType.Ring, total: this.stat.totalTask }).width(120)
Text(this.stat.finishTask + "/" + this.stat.totalTask)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
}.commonCardStyle()
.height(200)
.padding({ left: 20, right: 20 })
.justifyContent(FlexAlign.Center)
}
}
@Component
struct TaskListView {
@Consume stat: StatInfo
@State taskList: Array<Task> = []
handleStatistics() {
this.stat.finishTask = this.taskList.filter(item => item.isDone).length
this.stat.totalTask = this.taskList.length
}
build() {
Column() {
// 2.新增任务
Button("新增任务").onClick(() => {
this.taskList.push(new Task())
this.stat.totalTask = this.taskList.length
}).width("60%")
.margin({ top: 10 })
// 3.任务列表
List() {
ForEach(this.taskList, (item, index) => {
ListItem() {
this.TaskItemView(item)
}.swipeAction({ end: this.getDeleteButton(index) }) // 向左滑动,出现删除按钮
})
}.layoutWeight(1) // 高度权重
.width('100%')
.alignListItem(ListItemAlign.Center)
}
}
@Builder TaskItemView(task: Task) {
Row() {
// 这里不生效,原因:state 数组对象嵌套不刷新视图
if (task.isDone) {
Text(task.name)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.margin({ right: 40 })
.finishedTask()
} else {
Text(task.name)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.margin({ right: 40 })
}
Checkbox()
.select(task.isDone).onChange((isChecked) => {
task.isDone = isChecked
this.handleStatistics()
})
}.commonCardStyle()
.height(100)
.padding({ left: 20, right: 20 })
.justifyContent(FlexAlign.SpaceBetween)
}
@Builder getDeleteButton(index: number) {
Button({ type: ButtonType.Circle }) {
Image($r('app.media.del'))
}
.onClick(() => {
this.taskList.splice(index, 1)
this.handleStatistics()
})
.width(50)
.height(50)
.padding(10)
.margin({ right: 5 })
.backgroundColor('#ffffff')
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
@ObjectLink 和@Observed
@ObjectLink 和@Observed 装饰器用于在涉及嵌套对象或数组元素为对象的场景中进行双向数据同步。
使用说明
1.嵌套对象使用@Observed 修饰,比如下面
@Observed
class Task {
static id: number = 1
name: string = '任务' + Task.id++
isDone: boolean = false
}
- 1
- 2
- 3
- 4
- 5
- 6
2.需要根据嵌套对象属性的变更而刷新视图的组件,通过传入参数且该参数需要被@ObjectLink装饰
@Component
struct TaskItemView {
@ObjectLink task: Task
onTaskChange: () => void
build() {}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
代码示例
实现点击后任务名称样式变更,如下图所示,
@Observed
class Task {
static id: number = 1
name: string = '任务' + Task.id++
isDone: boolean = false
}
@Extend(Text) function finishedTask() {
.decoration({ type: TextDecorationType.LineThrough })
.fontColor('#B1B2B1')
}
@Styles function commonCardStyle() {
.width('95%')
.margin({ left: 10, right: 10, top: 10 })
.borderRadius(20)
.backgroundColor('#ffffff')
.shadow({ radius: 6, color: '#1f000000', offsetX: 2, offsetY: 4 })
}
class StatInfo {
totalTask: number = 0
finishTask: number = 0
}
@Entry
@Component
struct TaskPage {
// @Provide 跨组件传递且被@Provide装饰器修饰的组件不需要被传递
@Provide stat: StatInfo = new StatInfo()
build() {
Column() {
// 1.顶部任务统计部分
TaskStatisticsView()
// 2.任务列表 @Link 因为传递的是引用,必须使用$
TaskListView()
}
.width('100%')
.height('100%')
.backgroundColor('#eeeeee')
.justifyContent(FlexAlign.Start)
}
}
@Component
struct TaskStatisticsView {
@Consume stat: StatInfo
build() {
Row() {
Text("任务进度:")
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ right: 40 })
Stack() {
Progress({ value: this.stat.finishTask, type: ProgressType.Ring, total: this.stat.totalTask }).width(120)
Text(this.stat.finishTask + "/" + this.stat.totalTask)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
}.commonCardStyle()
.height(200)
.padding({ left: 20, right: 20 })
.justifyContent(FlexAlign.Center)
}
}
@Component
struct TaskListView {
@Consume stat: StatInfo
@State taskList: Array<Task> = []
handleStatistics() {
this.stat.finishTask = this.taskList.filter(item => item.isDone).length
this.stat.totalTask = this.taskList.length
}
build() {
Column() {
// 2.新增任务
Button("新增任务").onClick(() => {
this.taskList.push(new Task())
this.stat.totalTask = this.taskList.length
}).width("60%")
.margin({ top: 10 })
// 3.任务列表
List() {
ForEach(this.taskList, (item, index) => {
ListItem() {
// 变更,传入函数引用
TaskItemView({ task: item,onTaskChange:this.handleStatistics.bind(this) })
}.swipeAction({ end: this.getDeleteButton(index) }) // 向左滑动,出现删除按钮
})
}.layoutWeight(1) // 高度权重
.width('100%')
.alignListItem(ListItemAlign.Center)
}
}
@Builder getDeleteButton(index: number) {
Button({ type: ButtonType.Circle }) {
Image($r('app.media.del'))
}
.onClick(() => {
this.taskList.splice(index, 1)
this.handleStatistics()
})
.width(50)
.height(50)
.padding(10)
.margin({ right: 5 })
.backgroundColor('#ffffff')
}
}
@Component
struct TaskItemView {
@ObjectLink task: Task
onTaskChange: () => void
build() {
Row() {
// 这里不生效,原因:state 数组对象嵌套不刷新视图
if (this.task.isDone) {
Text(this.task.name)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.margin({ right: 40 })
.finishedTask()
} else {
Text(this.task.name)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.margin({ right: 40 })
}
Checkbox()
.select(this.task.isDone).onChange((isChecked) => {
this.task.isDone = isChecked
this.onTaskChange()
})
}.commonCardStyle()
.height(100)
.padding({ left: 20, right: 20 })
.justifyContent(FlexAlign.SpaceBetween)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
评论记录:
回复评论: