鸿蒙开发之状态管理@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装饰,就可以实现双向数据同步。

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

整体关系如下图

相关推荐
carrie呀carrie7 小时前
HarmonyOS:删除多层ForEach循环渲染的复杂数据而导致的一系列问题
开发语言·harmonyos·鸿蒙
SuperHeroWu711 小时前
【HarmonyOS】鸿蒙arrayBuffer和Uint8Array互相转化
华为·harmonyos·鸿蒙·arraybuffer·字节流··uint8array
play_big_knife18 小时前
鸿蒙项目云捐助第二十八讲云捐助项目首页组件云数据库加载轮播图
数据库·华为·harmonyos·鸿蒙·云开发·鸿蒙开发·鸿蒙技术
他的猫哎3 天前
鸿蒙 Navigation组件下的组件获取pageStack问题
harmonyos·鸿蒙
_Shirley3 天前
鸿蒙设置app更新跳转华为市场
android·华为·kotlin·harmonyos·鸿蒙
大土豆的bug记录3 天前
关于鸿蒙架构feature
华为·arkts·鸿蒙·arkui
SuperHeroWu73 天前
【HarmonyOS】HarmonyOS和React Native混合开发 (一)之环境安装
react native·harmonyos·鸿蒙·开发环境·环境安装·rn·混合开发
无处安放的波澜3 天前
【HarmonyOS 5.0】第十二篇-ArkUI公共属性(一)
华为·harmonyos·arkts·鸿蒙·鸿蒙系统
川石教育4 天前
鸿蒙开发-ArkTS 中使用 filter 组件
harmonyos·鸿蒙·鸿蒙应用开发·鸿蒙开发·鸿蒙开发培训·arkts语言
Damon小智4 天前
HarmonyOS NEXT 技术实践-实现音乐服务卡片
华为·harmonyos·鸿蒙·harmonyos next·服务卡片