一、介绍
资料来自官网:文档中心
在声明式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 = 0
build() {
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: number
build() {
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: Task
onTaskChange: () => void
build() {
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)
}
}
实现效果:
最后:👏👏😊😊😊👍👍