【鸿蒙 HarmonyOS 4.0】状态管理

news2024/12/28 21:13:36

一、介绍

资料来自官网:文档中心

在声明式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可以通过相同的变量名或者相同的变量别名绑定,变量类型必须相同。

下面代码变量名不一致,但具备相同的别名,使用@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)
  }
}

实现效果:

最后:👏👏😊😊😊👍👍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1465248.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】普通用户sudo失败怎么办

普通用户,sudo失败报错怎么办 问题分析如何解决成功 问题分析 新建的普通用户sudo失败 sudo提权,是以root的身份执行命令。 当我们用sudo提升权限的时候,这里有个问题,Linux会提示我们输入当前普通用户的密码——这就有点不好。…

骨传导耳机哪个品牌比较好?精选五大倍受好评的机型推荐!

近几年大家对保护听力健康的意识也越来越强烈了,骨传导耳机作为保护听力健康的不二之选,使用的人也越来越多,但是,在面对市场上鱼龙混杂的产品时,还是有很多人不知道该怎么去挑选一款性能优秀的骨传导耳机,…

[TCP] TCP/IP 基础知识词典(2)

我想统计一下,TCP/IP 尤其是TCP协议,能搜到的常见的问题,整理起来,关键词添加在目录中,便于以后查阅。 目前预计整理共3篇: [TCP] TCP/IP 基础知识问答 :基础知识 [TCP] TCP/IP 基础知识问答&…

硬盘坏了数据恢复怎么做?记好这2个方法!

“前段时间我的电脑硬盘出了点问题,不知道为什么就坏了。硬盘坏了数据恢复应该怎么做呢?请大家分享几个好用的方法吧!” 在数字化时代,硬盘作为数据存储的核心组件,其重要性不言而喻。然而,硬盘损坏是一个常…

【知识分享】自动化测试首选接口自动化?

在分层测试的“金字塔”模型中,接口测试属于第二层服务集成测试范畴。 相比UI自动化测试而言,接口自动化测试收益更大,且容易实现,维护成本低,有着更高的投入产出比。因此,项目开展自动化测试的首选一般为接…

记阿里云mysql丢表丢数据的实践记录

第一时间挂工单,联系工程师指引,现在回过来想,第一时间要确认发生时间。 1.通过性能视图(马后炮的总结,实际凭记忆恢复了三四次才找到数据) 2.先恢复数据 通过Navicat工具,结构同步&#xff0…

Vscode vim 插件使用Ctrl+C和V进行复制粘贴到剪切板

Vscode vim 插件使用CtrlC和V进行复制粘贴到剪切板 使用这一个插件的时候复制粘贴和其他软件互动的时候体验不好, 并且不可以用Ctrl c, Ctrl v很不爽 "vim.commandLineModeKeyBindings": [{"before" : ["Ctrl", "c"],"after&q…

C#,动态规划(DP)丢鸡蛋问题(Egg Dropping Puzzle)的三种算法与源代码

1 扔鸡蛋问题 动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。20世纪50年代初,美国数学家贝尔曼(R.Bellman)等人在研究多阶段决策过程的优化问题时&#xf…

3.deeplabv3+的深层网络结构的实现

在第一篇文章中我们提到“在encoder部分,主要包括了backbone(DCNN)、ASPP两大部分”,在这里的backbone就是mobilenetv2网络结构和xception网络结构,而ASPP结构就是深层网络结构,其网络结构如下:…

Anaconda和TensorFlow环境搭建!!

Anaconda下载 进入官网下载 https://www.anaconda.com/download 也可以通过清华的映像站下载: https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 我这里下载的是3.4.20版本。下载好就可以安装默认安装就行。 打开Anaconda Prompt修改成国内镜像 conda c…

Verilog刷题笔记36

题目: Create a 4-bit wide, 256-to-1 multiplexer. The 256 4-bit inputs are all packed into a single 1024-bit input vector. sel0 should select bits in[3:0], sel1 selects bits in[7:4], sel2 selects bits in[11:8], etc. 我的解法: module …

【蜂窝物联】医院WiFi全覆盖解决方案

项目背景 随着信息化程度的普及,无线网络覆盖的需求显得愈发突出,移动通信(GSM,3G,4G)的网络在很多区域无法满足客户的速率要求,而且不能满足某些特定场景的业务需求。医院是人流密集场所,进行…

C语言——指针——第1篇——(第19篇)

坚持就是胜利 文章目录 1.指针是什么2.指针和指针类型(1)指针 - 整数(2)指针 的 解引用 3.野指针(1)野指针成因1.指针未初始化2.指针越界访问3.指针指向的空间释放 (2)如何规避野指针1.指针初始化2.小心指针越界3.指针指向的空间…

Diehl EDI 项目案例

代傲Diehl 是一家拥有120多年历史的德国科技企业,凭借其多元化的产品在不同工业领域的各个业务线中备受好评。 由于Diehl的合作伙伴遍及全球,如何管理来自全球各地不同标准、不同格式的业务数据成为一大难题。EDI(Electronic Data Interchan…

win系统下安装php8.3版本并配置环境变量的详细教程

本篇文章主要讲解在win系统下安装和配置php8.3版本,并配置环境变量的详细教程。 日期:2024年2月22日 作者:任聪聪 一、下载php8.3版本包 php8.3版本官方下载地址:https://windows.php.net/download#php-8.3 步骤一、打开下载地址…

四、矩阵的分类

目录 1、相等矩阵 2、同形矩阵 3、方阵: 4、负矩阵、上三角矩阵、下三角矩阵: 5、对角矩阵:是方阵 ​编辑7、单位矩阵:常常用 E或I 来表示。它是一个方阵 8、零矩阵: 9、对称矩阵:方阵 1、相等矩阵 …

JAVA IDEA 项目打包为 jar 包详解

前言 如下简单 maven 项目,现在 maven 项目比较流行,你还没用过就OUT了。需要打包jar 先设置:点击 File > Project Structure > Artifacts > 点击加号 > 选择JAR > 选择From modules with dependencies 一、将所有依赖和模…

docker部署seata1.6.0

docker部署seata1.6.0 Seata 是 阿里巴巴 开源的 分布式事务中间件,解决 微服务 场景下面临的分布式事务问题。需要先搭建seata服务端然后与springcloud的集成以实现分布式事务控制的过程 ,项目中只需要在远程调用APi服务的方法上使用注解 GlobalTransa…

1.1_1 计算机网络的概念、功能、组成和分类

文章目录 1.1_1 计算机网络的概念、功能、组成和分类(一)计算机网络的概念(二)计算机网络的功能(三)计算机网络的组成1.组成部分2.工作方式3.功能组成 (四)计算机网络的分类 总结 1.…

2023 H1 中国边缘公有云服务市场 Top2,百度智能云加速推动分布式云智能化升级

近期,IDC 发布了《中国边缘云市场跟踪研究 2023 H1》。报告显示,2023 上半年,中国边缘公有云服务市场规模 24.3 亿元,同比增速达到 41.8%。 其中,百度智能云以 15.7% 的市场份额位列中国边缘公有云服务市场第二&#…