鸿蒙开发之状态管理@Prop和@Link

一、用法

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

结论:@Prop用于子组件只监听父组件的数据改变而改变,自己不对数据改变

@Link用于子组件与父组件都会对数据改变,都需要在数据改变的时候发生相应的更新。

二、@Prop和@Link区别

2.1数据同步的类型不同

@Prop的数据是单向传递的,父组件改变能通知子组件,但是子组件改变不能通知父组件。

@Link的数据是双向传递的,父组件改变能通知子组件,子组件的改变也可以通知父组件。

2.2 装饰的变量类型不同

@Prop可以装饰的类型有限

  • 只能支持string、number、boolean、enum类型。
  • 如果父组件是对象类型,可以传递对象的属性,子组件可以内部可以是对象的属性。
  • 不可以是数组、any类型

@Link可以装饰的类型较多

  • 支持string、number、boolen、enum、object、数组等
  • 数组的增、删、改都可以触发更新
  • 嵌套类型以及数组中对象的属性无法触发更新,类似@State
2.3装饰后属性传递方式不同

@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

@Provide和@Consume可以跨组件提供类似于@State和@Link的双向同步。

设想有三级组件父组件->子组件->孙子组件。

如果想要父组件与孙子组件中的数据达到双向同步,那么就需要先父组件与子组件绑定,然后子组件与孙子组件绑定,需要绑定多次。这个时候就可以用@Provide和@Consume装饰器了。

在父组件中用@Provide装饰,在孙子组件中采用@Consume装饰,就可以实现双向数据同步。

需要注意的是,使用这两个装饰器的时候不需要在父组件中传递参数。

整体关系如下图

相关推荐
●VON16 小时前
BodyAR 从零开始:开发环境搭建与完整项目配置指南
华为·harmonyos·鸿蒙·新特性
敲代码的鱼哇1 天前
PDF 预览与签名批注写回 支持安卓 iOS 鸿蒙 UTS插件
android·ios·pdf·鸿蒙·harmony
zhuweisky2 天前
uni-app 实现视频聊天、屏幕分享,支持Android、HarmonyOS、iOS
鸿蒙·视频聊天·屏幕分享
梦想不只是梦与想4 天前
鸿蒙 Live View Kit:实况窗服务(一)
harmonyos·鸿蒙·实况窗
空中海4 天前
HarmonyOS知识图谱和回顾
华为·知识图谱·鸿蒙
大雷神4 天前
HarmonyOS APP<<古今职鉴定>>开源教程第26篇:【完整案例】职业性格测试开发
harmonyos·arkts·鸿蒙·古今职鉴
therese_100865 天前
客户端设计(下):场景流派与实战设计方式
架构·安卓·鸿蒙
华清远见IT开放实验室5 天前
工程实战,应用导向:华清远见物联网“虚实融合“产教融合与实践教学方案
物联网·鸿蒙·全栈·星闪·虚拟仿真·无线传感网络
therese_100867 天前
客户端架构:为什么、什么时候、怎么做
设计模式·安卓·鸿蒙