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

news2024/11/18 7:23:47

        从今天开始,博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”,对于刚接触这项技术的小伙伴在学习鸿蒙开发之前,有必要先了解一下鸿蒙,从你的角度来讲,你认为什么是鸿蒙呢?它出现的意义又是什么?鸿蒙仅仅是一个手机操作系统吗?它的出现能够和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: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.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: 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 => {
          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 = url
    this.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.coloradmin.cn/o/1363160.html

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

相关文章

XML技术分析02

一、XML——树形结构 <?xml version"1.0" encoding"ISO-8859-1"?><note><to>George</to><from>John</from><heading>Reminder</heading><body>Dont forget the meeting!</body> </note…

VMware中删除虚拟机

虚拟机使用完成后&#xff0c;需要删除虚拟机如何操作呢&#xff1f; 1.首先进入VMware 2.选择需要删除的虚拟机&#xff0c;点击右键 3.直接选择“移除”&#xff1f; 当然不是&#xff0c;这只是从这么目录显示中去掉了&#xff0c;并非 “真正” 删除该虚拟机 注意&#x…

【XR806开发板使用】开发环境搭建、Hello工程以及开发事项

XR806开发板试用 很有幸能获得本次技术社区和全志组织的XR806开发板试用活动。之前开发的嵌入式应用都是在Windows平台上进行的&#xff0c;对于Linux下的开发并不熟悉&#xff0c;在社区里看到群友使用官方提供的docker环境进行开发&#xff0c;顺着群友的指导&#xff0c;找…

Linux多线程基础(2):僵尸线程和资源回收

在Linux多线程基础(1)中,我给大家介绍了多线程该如何创建,这一篇文章我们对僵尸进程以及如何回收线程进行讲解. 1.僵尸线程 僵尸线程的产生是因为主线程在子线程结束之前退出&#xff0c;导致子线程的状态无法被回收&#xff0c;从而形成了僵尸线程. 底层原理是线程有joinab…

Unity中Shader的_Time精度问题

文章目录 前言一、U方向上优化二、V方向上优化在这里插入图片描述 三、最终代码1、效果2、Shader 前言 在Unity的Shader中&#xff0c;使用了_Time来达到UV的流动效果&#xff0c;普遍会出现一个问题。我们的UV值会随着时间一直增加&#xff08;uv值增加了&#xff0c;但是因为…

三大主要排序方法总结:快速排序,选择排序,冒泡排序

本文介绍&#xff1a;三大排序方法&#xff08;快速排序&#xff0c;选择排序&#xff0c;冒泡排序&#xff09;&#xff08;后续期间可能会发布一篇关于qsort函数的文章&#xff09; 自我介绍&#xff1a;一个脑子不好的大一学生&#xff0c;c语言接触还没到半年&#xff0c;…

【Pytorch】学习记录分享11——GAN对抗生成网络

PyTorch GAN对抗生成网络 0. 工程实现1. GAN对抗生成网络结构2. GAN 构造损失函数&#xff08;LOSS&#xff09;3. GAN对抗生成网络核心逻辑3.1 参数加载&#xff1a;3.2 生成器&#xff1a;3.3 判别器&#xff1a; 0. 工程实现 原理解析&#xff1a; 论文解析&#xff1a;GAN…

LLM Agent之RAG的反思:放弃了压缩还是智能么?

已经唠了三章的RAG&#xff0c;是时候回头反思一下&#xff0c;当前的RAG是解决幻觉的终点么&#xff1f;我给不出直接的答案&#xff0c;不过感觉当前把RAG当作传统搜索框架在大模型时代下的改良&#xff0c;这个思路的天花板高度有限~ 反思来源于对RAG下模型回答的直观感受&…

DrGraph原理示教 - OpenCV 4 功能 - 直方图

OpenCV直方图是一种可以对整幅图的灰度分布进行整体了解的图示。它是带有像素值&#xff08;从0到255&#xff0c;不总是&#xff09;的图在X轴上&#xff0c;在y轴上的图像对应的像素个数。通过观察图像的直方图&#xff0c;我们可以直观的了解图像的对比度、亮度、亮度分布等…

数据分析——快递电商

一、任务目标 1、任务 总体目的——对账 本项目解决同时使用多个快递发货&#xff0c;部分隔离区域出现不同程度涨价等情形下&#xff0c;如何快速准确核对账单的问题。 1、在订单表中新增一列【运费差异核对】来表示订单运费实际有多少差异&#xff0c;结果为数值。 2、将…

HarmonyOS 开发基础(六)Slider

HarmonyOS 开发基础&#xff08;六&#xff09;Slider Entry Component struct Index {build() {Row() {Column() {// Slider&#xff1a;ArkUI 的基础组件 滑动条组件// options 参数&#xff1a;Slider 基础设置Slider({// 最小值min: 20,// 最大值max: 200,// 当前值value: …

文心一言 VS 讯飞星火 VS chatgpt (173)-- 算法导论13.3 2题

二、用go语言&#xff0c;将关键字41、38、31、12、19、8连续地插入一棵初始为空的红黑树之后&#xff0c;试画出该结果树。 文心一言&#xff1a; 首先&#xff0c;我们需要了解红黑树的性质。红黑树是一种自平衡二叉查找树&#xff0c;其中每个节点要么是红色&#xff0c;要…

使用sentinel作为熔断器

什么是sentinel Sentinel&#xff0c;中文翻译为哨兵&#xff0c;是为微服务提供流量控制、熔断降级的功能&#xff0c;它和Hystrix提供的功能一样&#xff0c;可以有效的解决微服务调用产生的“雪崩”效应&#xff0c;为微服务系统提供了稳定性的解决方案。随着Hytrxi进入了维…

7+非肿瘤+WGCNA+机器学习+诊断模型,构思巧妙且操作简单

今天给同学们分享一篇生信文章“Platelets-related signature based diagnostic model in rheumatoid arthritis using WGCNA and machine learning”&#xff0c;这篇文章发表在Front Immunol期刊上&#xff0c;影响因子为7.3。 结果解读&#xff1a; DEGs和血小板相关基因的…

Flink自定义Source模拟数据流

maven依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.…

CSS3 边框border、outline、box-shadow

1 border 语法&#xff1a;border: width style color 2 outline 语法&#xff1a;outline: width style color 2.1 outline-offet MDN解释&#xff1a;用于设置outline与一个元素边缘或边框之间的间隙 即&#xff1a;设置outline相对border外边缘的偏移&#xff0c;可以为…

Python 全栈体系【四阶】(十一)

第四章 机器学习 机器学习&#xff1a; 传统的机器学习&#xff1a;以算法为核心深度学习&#xff1a;以数据和计算为核心 感知机 perceptron&#xff08;人工神经元&#xff09; 可以做简单的分类任务掀起了第一波 AI 浪潮 感知机不能解决线性不可分问题&#xff0c;浪潮…

RouterOS L2TP安装与配置

申明&#xff1a;本文仅针对国内L2TP/PPTP&#xff0c;适用于国内的游戏加速或学术研究&#xff0c;禁止一切利用该技术的翻墙行为。 1. L2TP介绍 L2TP&#xff08;Layer 2 Tunneling Protocol&#xff09;是一种在计算机网络中广泛使用的隧道协议&#xff0c;它被设计用于通过…

java火车查询管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java Web火车查询管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql…

2023年12月 C/C++(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C++编程(1~8级)全部真题・点这里 第1题:数的输入和输出 输入一个整数和双精度浮点数,先将浮点数保留2位小数输出,然后输出整数。 时间限制:1000 内存限制:65536 输入 一行两个数,分别为整数N(不超过整型范围),双精度浮点数F,以一个空格分开。 输出 一行两个数,分…