【HarmonyOS】装饰器下的状态管理与页面路由跳转实现

        从今天开始,博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”,对于刚接触这项技术的小伙伴在学习鸿蒙开发之前,有必要先了解一下鸿蒙,从你的角度来讲,你认为什么是鸿蒙呢?它出现的意义又是什么?鸿蒙仅仅是一个手机操作系统吗?它的出现能够和Android和IOS三分天下吗?它未来的潜力能否制霸整个手机市场呢?

抱着这样的疑问和对鸿蒙开发的好奇,让我们开始今天对ArkUI状态管理的掌握吧!

目录

ArkUI状态管理

@State装饰器

@Provide和@Consume

页面路由


ArkUI状态管理

在声明式UI中是以状态来驱动视图进行更新的,其中的核心概念就是状态和视图所谓状态就是驱动视图更新这个数据,或者说是我们自定义组件当中定义好的那些被装饰器标记好的变量;所谓视图就是指GUI描述渲染得到的用户界面;视图渲染好了之后用户就可以对视图中的页面元素产生交互,通过点击、触摸、拖拽等互动事件来改变状态变量的值,在arkui的内部就有一种机制去监控状态变量的值,一旦发现它发生了变更就会去触发视图的重新渲染。所以像这种状态和视图之间的相互作用的机制,我们就称之为状态管理机制。

状态管理需要用到多个不同的装饰器,接下来我们开始学习状态管理的基本概念以及以下几个装饰器的基本用法和注意事项。

@State装饰器

使用@State装饰器有以下注意事项:

1)@State装饰器标记的变量必须初始化,不能为空值

2)@State支持Object、class、string、number、boolean、enum类型以及这些类型的数组

3)嵌套类型(Object里面的某个属性又是一个Object)以及数组中的对象属性无法触发视图更新,以下是演示代码:

class Person {name: stringage: numberconstructor(name: string, age: number) {this.name = namethis.age = age}
}
@Entry
@Component
struct StatePage {idx: number = 1@State p: Person[] = [new Person('张三', 20)]build(){Column(){Button('添加').onClick(()=>{this.p.push(new Person('张三'+this.idx++, 20 ))})ForEach(this.p,(p, index) => {Row(){Text(`${p.name}: ${p.age}`).fontSize(30).onClick(() => {//数组内的元素变更不会触发数组的重新渲染// p.age++//数组重新添加、删除或者赋值的时候才会触发数组的重新渲染this.p[index] = new Person(p.name, p.age+1)})Button('删除').onClick(()=>{this.p.splice(index, 1)})}.width('100%').justifyContent(FlexAlign.SpaceAround)})}.width('100%').height('100%')}
}

Prop和Link这两个装饰器是在父子组件之间数据同步的时候去使用的,以下是两者的使用情况:

装饰器@Prop@Link
同步类型单向同步双向同步
允许装饰的变量类型

1)@Prop只支持string、number、boolean、enum类型

2)父组件是对象类型,子组件是对象属性

3)不可以是数组、any

1)父子类型一致:string、number、boolean、enum、object、class,以及他们的数组

2)数组中的元素增、删、替换会引起刷新

3)嵌套类型以及数组中的对象属性无法触发视图更新

接下来借助Prop和Link完成一个小案例:

我们在父组件中通过prop向子组件传值,子组件通过@Prop装饰器接受到值之后进行页面渲染,这里我们采用了ArkUI提供的堆叠容器和进度条组件实现页面的配置:

// 统一卡片样式
@Styles function card(){.width('95%').padding(20).backgroundColor(Color.White).borderRadius(15).shadow({radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4})
}@Component
export struct TaskStatistics {@Prop totalTask: number // 总任务数量@Prop 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()).fontColor('#36D').fontSize(24)Text(' / ' + this.totalTask.toString()).fontSize(24)}}}.margin({top: 20, bottom: 10}).justifyContent(FlexAlign.SpaceEvenly).card()}
}

子组件如果修改父组件的值的话,需要通过装饰器@Link来实现,父组件需要通过$来拿值:

// 任务类
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})
}@Component
export struct TaskList {@Link totalTask: number // 总任务数量@Link finishTask: number // 已完成任务数量@State tasks: Task[] = [] // 任务数组// 任务更新触发函数handleTaskChange(){this.totalTask = this.tasks.length // 更新任务总数量this.finishTask = this.tasks.filter(item => item.finished).length // 更新任务数量}build() {Column(){// 任务新增按钮Button('新增任务').width(200).onClick(()=>{this.tasks.push(new Task()) // 新增任务数组this.handleTaskChange()})// 任务列表List({space: 10}){ForEach(this.tasks,(item: Task, index)=>{ListItem(){Row(){Text(item.name).fontSize(20)Checkbox().select(item.finished).onChange(val => {item.finished = val // 更新当前的任务状态this.handleTaskChange()})}.card().justifyContent(FlexAlign.SpaceBetween)}.swipeAction({end: this.DeleteButton(index)})})}.width('100%').layoutWeight(1).alignListItem(ListItemAlign.Center)}}@Builder DeleteButton(index: number){Button(){Image($r('app.media.delete')).fillColor(Color.Red).width(20)}.width(40).height(40).type(ButtonType.Circle).backgroundColor(Color.Red).margin(5).onClick(()=>{this.tasks.splice(index, 1)this.handleTaskChange()})}
}

接下来就需要在父组件引用这两个子组件了,然后传参来获取和传递相关数值:

// 任务类
class Task {static id: number = 1 // 静态变量,内部共享name: string = `任务${Task.id++}` // 任务名称finished: boolean = false // 任务状态,是否已完成
}import { TaskStatistics } from '../components/TaskStatistics'
import { TaskList } from '../components/TaskList'
@Entry
@Component
struct PropPage {@State totalTask: number = 0 // 总任务数量@State finishTask: number = 0 // 已完成任务数量@State tasks: Task[] = [] // 任务数组build(){Column({space: 10}){// 任务进度卡片TaskStatistics({ totalTask: this.totalTask, finishTask: this.finishTask })// 任务列表TaskList({ totalTask: $totalTask, finishTask: $finishTask })}.width('100%').height('100%').backgroundColor('#F1F2F3')}
}

最终呈现的结果如下:

@Provide和@Consume

这两个装饰器可以跨组件提供类似@State和@Link的双向同步,操作方式很简单,父组件之间使用Provide装饰器,子组件全部使用Consume装饰器,父组件都不需要传递参数了,直接调用子组件函数即可:

最终呈现的结果如下:

虽然相对来说比Prop和Link简便许多,但是使用Provide和Consume还是有代价的,本来需要传递参数的,但是使用Provide不需要传递参数,其内部自动帮助我们去维护,肯定是有一些资源上的浪费,所以说我们能用Prop还是尽量用Prop,实在用不了的可以去考虑Provide。

这两个装饰器用于在涉及嵌套对象或数组元素为对象的场景中进行双向数据同步:

我们给任务列表中的文本添加一个样式属性,当我们点击勾选的话,文本就会变灰并且加上一个中划线样式:

但是当我们勾选之后,视图并没有发生变化,原因是我们的Task是一个对象类型,数组的元素是对象,对象的属性发生修改是不会触发视图的重新渲染的,所以这里我们需要使用本次讲解的装饰器来进行解决:

我们给class对象设置@Observed装饰器:

然后在要修改对象属性值的位置进行设置@ObjectLink装饰器,因为这里一个任务列表通过ForEach遍历出来的,所以我们需要将这个位置单独抽离出来形成一个函数,然后将要使用的item设置@ObjectLink装饰器,因为还需要调用函数,但是任务列表的函数不能动,所以我们也将调用的函数作为参数传递过去:

@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 => {this.item.finished = val // 更新当前的任务状态this.onTaskChange()})}.card().justifyContent(FlexAlign.SpaceBetween)}
}

传递过程中为了确保this指向没有发生改变,我们在传递函数的时候,还需要通过bind函数指定this指向:

最终呈现的结果如下:

页面路由

页面路由是指在应用程序中实现不同页面之间的跳转和数据传递,如果学习过前端vue或react框架的人,可以非常简单的理解页面路由跳转的概念,以下是在鸿蒙开发中进行页面路由跳转所调用的API函数以及相应函数的作用,与前端的vue框架十分类似:

Router有两种页面跳转模式,分别是:

router.pushUrl():目标页不会替换当前页,而是压入页面栈,因此可以用router.back()返回当前页

router.replaceUrl():目标页替换当前页,当前页会被销毁并释放资源,无法返回当前页

Router有两种页面实例模式,分别是:

Standard:标准实例模式,每次跳转都会新建一个目标页并压入栈顶,默认就是这种模式

Single:单实例模式,如果目标页已经在栈中,则离栈顶最近的同url页面会被移动到栈顶并重新加载

了解完页面路由基本概念之后,接下来在案例中开始介绍如何使用页面路由:

首先我们在index首页定义路由信息:

// 定义路由信息
class RouterInfo {url: string // 页面路径title: string // 页面标题constructor(url: string, title: string) {this.url = urlthis.title = title}
}

接下在struct结构体里面定义路由相关信息以及页面的静态样式:

  @State message: string = '页面列表'private routers: RouterInfo[] = [new RouterInfo('pages/router/test1', '页面1'),new RouterInfo('pages/router/test2', '页面2'),new RouterInfo('pages/router/test3', '页面3'),new RouterInfo('pages/router/test4', '页面4')]build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold).fontColor('#008c8c').height(80)List({space: 15}){ForEach(this.routers,(router, index) => {ListItem(){this.RouterItem(router, index + 1)}})}.layoutWeight(1).alignListItem(ListItemAlign.Center).width('100%')}.width('100%').height('100%')}}

定义RouterItem函数,设置点击函数进行路由跳转:

@Builder RouterItem(r: RouterInfo, i: number){Row(){Text(i+'.').fontSize(20).fontColor(Color.White)Blank()Text(r.title).fontSize(20).fontColor(Color.White)}.width('90%').padding(12).backgroundColor('#38f').shadow({radius: 6, color: '#4f0000', offsetX: 2, offsetY: 4}).onClick(()=>{// router跳转,传递3个参数router.pushUrl(// 跳转路径及参数{url: r.url,params: {id: i}},// 页面实例router.RouterMode.Single,// 跳转失败的一个回调err => {if (err) {console.log(`跳转失败,errCode:${err.code} errMsg: ${err.message}`)}})})}

定义3个路由跳转页,设置第四个路由没有跳转页面,作对照:

注意,如果是仅仅是新建一个ArkTS页面的话需要在以下的文件中进行路由配置:

如果觉得每次创建一个页面都要进行一次路由路径的创建比较烦的话,可以采用以下创建方式,会自动帮我们配置好路由路径,而不需在去手动设置:

在子组件中,如果我们想拿到传递过来的参数可以调用getParams函数,返回调用back函数:

想要加个返回的警告可以采用如下的方式:

最终呈现的结果为:

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

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

相关文章

小白入门基础 - Restful

一:REST与RESTful: REST:表现层状态转移,资源在网络中以某种形式进行状态转移。 RESTful是基于REST理念的一套开发风格,是具体的开发规则。 服务器端只返回数据,以json或者xml的格式。 RESTful开发规范&a…

八大算法排序@归并排序(C语言版本)

目录 归并排序概念算法思想第一步第二步第三步 算法步骤代码实现代码1代码优化 时间复杂度空间复杂度特性总结 归并排序 概念 归并排序(Merge Sort)是一种基于分治策略的经典排序算法。它的基本思想是将待排序的数组划分成两个子数组,分别对…

大模型实战笔记02——大模型demo

大模型实战笔记02——大模型demo 1、大模型及InternLM模型介绍 2、InternLM-Chat-7B智能对话Demo 3、Lagent智能体工具调用Demo 4、浦语灵笔图文创作理解Demo 5、通用环境配置 注 笔记图片均为视频截图 笔记课程视频地址:https://www.bilibili.com/video/BV1Ci4y1…

基于 IP 多播的网络会议程序(2024)

1.题目描述 局域网 IP 多播程序&#xff0c;设计一个图形界面的网络会议程序&#xff08;实现文本多播方式即可&#xff09;。 2.演示Demo 3.参考代码 广播发送代码 //服务端 #include <winsock2.h> #include <iostream> #include <list>#pragma comment(l…

顶顶通呼叫中心中间件通过队列外呼拨打另一个sip并且放音(mod_cti基于FreeSWITCH)

介绍 顶顶通呼叫中心中间件通过队列外呼拨打另一个sip并且放音 一、添加acl 打开ccadmin->点击配置文件->点击acl.conf->在</list>后面添加一条图中的信息->muqi是我自己设置的名字你们可以修改为自己需要的名字->添加好了点击提交XML->在运维调试点…

Redis概览

Redis存储是Key-Value结构的数据&#xff0c;其中Key是字符串类型&#xff0c;Value有5种常见的数据类型 字符串 String 哈希 hash 列表 list 集合 set 有序集合 sorted set / zset 各种数据类型的特性 字符串操作命令 : ● SET ke…

LeetCode 2125. 银行中的激光束数量【数组,遍历】1280

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

技术学习周刊第 1 期

2018 年参与过 1 年的 ARTS 打卡&#xff0c;也因为打卡有幸加入了 MegaEase 能与皓哥&#xff08;左耳朵耗子&#xff09;共事。时过境迁&#xff0c;皓哥已经不在了&#xff0c;自己的学习梳理习惯也荒废了一段时间。 2024 年没给自己定具体的目标&#xff0c;只要求自己好好…

电话号码信息收集工具:PhoneInfoga | 开源日报 No.137

sundowndev/phoneinfoga Stars: 11.2k License: GPL-3.0 PhoneInfoga 是一个用于扫描国际电话号码的信息收集框架&#xff0c;它允许用户首先收集基本信息 (如国家、地区、运营商和线路类型)&#xff0c;然后使用各种技术来尝试找到 VoIP 提供商或识别所有者。该工具与一系列必…

[足式机器人]Part2 Dr. CAN学习笔记-动态系统建模与分析 Ch02-7二阶系统

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-动态系统建模与分析 Ch02-7二阶系统 1. 二阶系统对初始条件的动态响应 Matlab/Simulink - 2nd Order Syetem Response to IC2. 二阶系统的单位阶跃响应 2nd Order System Unit Step Response3. 二…

copilot在pycharm的应用

Copilot在PyCharm中的应用 一、引言 随着人工智能技术的飞速发展&#xff0c;AI在编程领域的应用也越来越广泛。Copilot&#xff0c;作为一款由微软开发的AI编程助手&#xff0c;已经引起了广大开发者的关注。它利用深度学习技术&#xff0c;通过分析大量开源代码&#xff0c…

Linux-v4l2框架

框架图 从上图不难看出&#xff0c;v4l2_device作为顶层管理者&#xff0c;一方面通过嵌入到一个video_device中&#xff0c;暴露video设备节点给用户空间进行控制&#xff1b;另一方面&#xff0c;video_device内部会创建一个media_entity作为在media controller中的抽象体&a…

unity中0GC优化方案《zstring》

文章目录 序言简介GC带来的问题性能瓶颈玩家体验受损 使用方式 序言 游戏开发秉承遇到好东西要分享&#xff0c;下面介绍zstring&#xff0c;感谢作者开源无私奉献 源码地址&#xff1a;https://github.com/871041532/zstring 简介 GC带来的问题 性能瓶颈 GC暂停主线程执行…

docker 安装elasticsearch、kibana、cerebro

安装步骤 第一步安装 docker 第二步 拉取elasticsearch、kibana、cerebro 镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.10.2 docker pull docker.elastic.co/kibana/kibana:7.10.2 docker pull lmenezes/cerebro:latest第三步、创建 容器 创建e…

设计模式之中介者模式【行为型模式】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档> 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某…

[Kafka集群] 配置支持Brokers内部SSL认证\外部客户端支持SASL_SSL认证并集成spring-cloud-starter-bus-kafka

目录 Kafka 集群配置 准备 配置流程 Jaas(Java Authentication and Authorization Service )文件 zookeeper 配置文件 SSL自签名 启动zookeeper集群 启动kafka集群 spring-cloud-starter-bus-kafka 集成 Kafka 集群配置 准备 下载统一版本Kafka服务包至三台不同的服…

安卓Android Studio读写FM1208CPU卡源码

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?spma1z10.5-c-s.w4002-21818769070.11.6c46789elLwMzv&id615391857885 <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout x…

Winform中使用Websocket4Net实现Websocket客户端并定时存储接收数据到SQLite中

场景 SpringBootVue整合WebSocket实现前后端消息推送&#xff1a; SpringBootVue整合WebSocket实现前后端消息推送_websocket vue3.0 springboot 往客户端推送-CSDN博客 上面实现ws推送数据流程后&#xff0c;需要在windows上使用ws客户端定时记录收到的数据到文件中&#x…

vue3 的内置组件汇总

官方给出的说明&#xff1a; Fragment: Vue 3 组件不再要求有一个唯一的根节点&#xff0c;清除了很多无用的占位 div。Teleport: 允许组件渲染在别的元素内&#xff0c;主要开发弹窗组件的时候特别有用。Suspense: 异步组件&#xff0c;更方便开发有异步请求的组件。 一、fr…

模仿Activiti工作流自动建表机制,实现Springboot项目启动后自动创建多表关联的数据库与表的方案

文/朱季谦 熬夜写完&#xff0c;尚有不足&#xff0c;但仍在努力学习与总结中&#xff0c;而您的点赞与关注&#xff0c;是对我最大的鼓励&#xff01; 在一些本地化项目开发当中&#xff0c;存在这样一种需求&#xff0c;即开发完成的项目&#xff0c;在第一次部署启动时&…