【HarmonyOS】装饰器下的状态管理与页面路由跳转实现
从今天开始,博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”,对于刚接触这项技术的小伙伴在学习鸿蒙开发之前,有必要先了解一下鸿蒙,从你的角度来讲,你认为什么是鸿蒙呢?它出现的意义又是什么?鸿蒙仅仅是一个手机操作系统吗?它的出现能够和Android和IOS三分天下吗?它未来的潜力能否制霸整个手机市场呢?
抱着这样的疑问和对鸿蒙开发的好奇,让我们开始今天对ArkUI状态管理的掌握吧!
目录
ArkUI状态管理
@State装饰器
@Prop和@Link
@Provide和@Consume
@Observed和@ObjectLink
页面路由
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这两个装饰器是在父子组件之间数据同步的时候去使用的,以下是两者的使用情况:
装饰器 | @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。
@Observed和@ObjectLink
这两个装饰器用于在涉及嵌套对象或数组元素为对象的场景中进行双向数据同步:
我们给任务列表中的文本添加一个样式属性,当我们点击勾选的话,文本就会变灰并且加上一个中划线样式:
但是当我们勾选之后,视图并没有发生变化,原因是我们的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函数:
想要加个返回的警告可以采用如下的方式:
最终呈现的结果为:
相关文章:

【HarmonyOS】装饰器下的状态管理与页面路由跳转实现
从今天开始,博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”,对于刚接触这项技术的小伙伴在学习鸿蒙开发之前,有必要先了解一下鸿蒙,从你的角度来讲,你认为什么是鸿蒙呢?它出现的意义又是…...

学习笔记——C++中数据的输入 cin
作用:用于从键盘中获取数据 关键字:cin 语法:cin>>变量 类型:C中数据的输入主要包含:整形(int)浮点型(float,double float),字符型&…...

Filter Options in Select Field
Filter Options in Select Field 假设有两个下拉字段State和City。邦有两个值卡纳塔克邦和马哈拉施特拉邦,城市有四个值,班加罗尔,迈索尔,孟买和浦那。如果希望根据State中选择的值过滤City中的选项,可以编写如下所示的…...

【React系列】Hook(二)高级使用
本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. Hook高级使用 1.1. useReducer 很多人看到useReducer的第一反应应该是redux的某个替代品,其实并不是…...
编程笔记 html5cssjs 018 HTML颜色
编程笔记 html5&css&js 018 HTML颜色 一、HTML 颜色二、HTML中设置颜色值三、颜色名称和颜色值 颜色是视觉中重要因素,尤其是处理人机界面中,更是要处理颜色设置和搭配。在网页中,提供了设置颜色的一些方案,需要我们认真学…...

C++_继承
介绍 继承的基本概念 1.共性和个性 (PS:有相同的属性 但是 又有自己的特点) 基类和子类 1. 基类(父类) 共性 2. 子类(派生类) 个性(特点) 继承语法 1.class 子类名:继承方式1 基类1,继承方式2 基类2{ 行为 };继承方式(PS:默认继承方式为:私有继承) 1.公有继承: public 2.保护…...

Java-IO流-15
文件操作 文件创建 package com.edu.file;import org.junit.jupiter.api.Test;import java.io.File; import java.io.IOException;public class Demo01 {public static void main(String[] args) {}Test//方式1public void create01(){String filePath "D:\\new1.txt&q…...

java中使用redis
1、redis数据类型 1.1、5种数据类型 redis存储的是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型:字符串 string、哈希 hash、列表 list、集合 set、有序集合 sorted set / zset。 字符串(string):普通字符…...
Mongodb的可重试读操作
目录 重试读操作 需要条件 启用重读 支持可重试读的操作 不支持可重试读的操作 行为 重试读操作 连接mongodb进行读操作时,遇到网络或数据库集群的主节点切换导致的数据读问题。mongodb驱动自动尝试重新执行读操作。 需要条件 数据库连接驱动支持mongodb4.2…...
2024年1月2日-1月7日(ue5底层渲染+ue arpg+项目需求)
按照月计划,按照每小时分四段进行,arpg一例ue5底层渲染0.1小时arpg一例项目需求的相关视频教程一段 周二: 18:30- 19:30(1小时)ue arpg (88-89) ue5底层渲染03A14(6:08)…...
MySQL中的视图和触发器
SQL 视图 1 ) 概述 在mysql中,视图是一个非真实存在的虚拟表其本质是,根据sql语句获取动态的数据集,并为其命名用户使用时只需使用名称,即可获取结果集,并可以将其当做表来使用 2 )用法示例 2.1 比较麻烦…...
uView-UI v2.x常见问题整理
为了更好的给大家提供 uView UI 的技术支持,uView UI 团队整理常见问题文档,大家可以阅读查找常见的问题解决办法。 uView 2.x 文档 https://www.uviewui.com uView 1.x 文档 https://v1.uviewui.com uView UI uni-app 主页 DCloud 插件市场 uVie…...

MBTI职业性格测试 28题(免费版)
MBTI职业性格测试概述 MBTI是现在国际上最为流行的测试工具,利用MBTI职业性格测试,可以清楚地找到自己的性格特点以及兴趣爱好,方便于对职业进行规划、以及改善人际关系。其主要应用心理学常识对个性做出判断,提炼出动力、信息收…...

Springcloud 微服务实战笔记 Ribbon
使用 Configurationpublic class CustomConfiguration {BeanLoadBalanced // 开启负载均衡能力public RestTemplate restTemplate() {return new RestTemplate();}}可看到使用Ribbon,非常简单,只需将LoadBalanced注解加在RestTemplate的Bean上࿰…...

CSS基础笔记-04cascade-specificity-inheritance
CSS基础笔记系列 《CSS基础笔记-01CSS概述》《CSS基础笔记-02动画》CSS基础笔记-03选择器 前言 Cascading Style Sheets,关键就在于这个cascading,对于这个术语理解,感觉对于我这种CSS新手有点儿不太friendly。本文记录下我对这个术语的理…...

Spring应用的部署与管理
一、前言 部署是将开发好的应用发布到服务器上,使其能够被用户访问的关键步骤。Spring框架提供了灵活的部署选项,本文将介绍Spring应用的常见部署方式和一些建议,帮助开发者顺利将应用投放到生产环境。 二、传统部署方式:WAR包 传…...

B端产品经理学习-需求挖掘
B端产品需求挖掘 目录 识别和管理干系人 决策人和负责人需求挖掘 针对用户进行需求挖掘 用户访谈结果整理 B端产品的需求来源是非常复杂的,要考虑多个方面;如果你是一个通用性的产品,要考虑市场、自身优劣势、干系人。而定制型B端产品会…...

整数规划基本原理
1.1 定义 规划中的变量(部分或全部)限制为整数时,称为整数规划。若在线性规划模型中,变量限制为整数,则称为整数线性规划。目前所流行的求解整数规划的方法,往往只适用于整数线性规划。目前还没有一种方法…...

秋招复习之堆
目录 前言 堆 堆的常用操作 堆的实现(大根堆) 1. 堆的存储与表示 2. 访问堆顶元素 3. 元素入堆 4. 堆顶元素出堆 Top-k 问题 方法一:遍历选择 方法二:排序 方法三:堆 总结 前言 秋招复习之堆。 堆 「堆 heap…...

算法训练营Day36(贪心-重叠区间)
都算是 重叠区间 问题,大家可以好好感受一下。 都属于那种看起来好复杂,但一看贪心解法,惊呼:这么巧妙! 还是属于那种,做过了也就会了,没做过就很难想出来。 不过大家把如下三题做了之后&#…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...

(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...
6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙
Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...