当前位置: 首页 > news >正文

【鸿蒙 HarmonyOS 4.0】状态管理

一、介绍

资料来自官网:文档中心

在声明式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 = 0build() {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: numberbuild() {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: TaskonTaskChange: () => voidbuild() {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)}
}

实现效果:

最后:👏👏😊😊😊👍👍

相关文章:

  • 【更换yarn的位置】解决yarn和nodejs不在同一盘下产生的某些命令应用失败问题
  • Python实战:xlsx文件的读写
  • [程序员] sipp运行时socket接收队列持续满载 - 文件系统访问慢
  • PostgreSQL 的实体化视图介绍
  • android 15
  • 服务器丢包的原因及解决方法
  • Vue30 自定义指令 函数式 对象式
  • react18加antd新手上路使用
  • Golang 并发 Channel的用法
  • 智慧物业信息管理系统平台及APP建设项目
  • 第2讲:C语言数据类型和变量
  • 代理模式笔记
  • 【坑】Spring Boot整合MyBatis,一级缓存失效
  • 微服务三十五关
  • Windows 10 优化指南20240223
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • 【面试系列】之二:关于js原型
  • Android交互
  • DataBase in Android
  • mysql 5.6 原生Online DDL解析
  • nfs客户端进程变D,延伸linux的lock
  • Redis 懒删除(lazy free)简史
  • 从零开始在ubuntu上搭建node开发环境
  • 动态规划入门(以爬楼梯为例)
  • 力扣(LeetCode)965
  • 思否第一天
  • 通过几道题目学习二叉搜索树
  • 一道闭包题引发的思考
  • 再次简单明了总结flex布局,一看就懂...
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • 回归生活:清理微信公众号
  • #define与typedef区别
  • #includecmath
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • (175)FPGA门控时钟技术
  • (2009.11版)《网络管理员考试 考前冲刺预测卷及考点解析》复习重点
  • (LeetCode) T14. Longest Common Prefix
  • (ros//EnvironmentVariables)ros环境变量
  • (二)fiber的基本认识
  • (附源码)springboot猪场管理系统 毕业设计 160901
  • (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
  • (汇总)os模块以及shutil模块对文件的操作
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (十一)c52学习之旅-动态数码管
  • (实战)静默dbca安装创建数据库 --参数说明+举例
  • *1 计算机基础和操作系统基础及几大协议
  • .net core webapi 大文件上传到wwwroot文件夹
  • .net framework 4.0中如何 输出 form 的name属性。
  • .net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .pyc文件还原.py文件_Python什么情况下会生成pyc文件?
  • @EnableWebMvc介绍和使用详细demo
  • [ 攻防演练演示篇 ] 利用通达OA 文件上传漏洞上传webshell获取主机权限
  • [ 英语 ] 马斯克抱水槽“入主”推特总部中那句 Let that sink in 到底是什么梗?
  • [android] 天气app布局练习