写给初学者的 HarmonyOS 教程 -- 状态管理(@State/@Prop/@Link 装饰器)

@State 装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI 会发生对应的渲染改变(类似 Compose 的 mutablestateof )。

@Prop 装饰的变量可以和父组件建立单向的同步关系。@Prop 装饰的变量是可变的,但是变化不会同步回其父组件。

@Lin k装饰的变量可以与其父组件中对应的数据源建立双向数据绑定。

@State 装饰器使用规则

@State变量装饰器说明
装饰器参数
同步类型不与父组件中任何类型的变量同步
允许装饰的变量类型Object、class、string、number、boolean、enum 类型,以及这些类型的数组。
类型必须被指定。
不支持 any,不支持简单类型和复杂类型的联合类型,不允许使用 undefined 和 null。
被装饰变量的初始值必须本地初始化

@State 装饰器使用场景

装饰简单类型的变量

@Entry
@Component
struct Index {@State count: number = 0;build() {Column() {Button() {Text(`click times: ${this.count}`).margin(20)}.onClick(() => {this.count += 1;}).width('100%')}.margin(20).height('100%')}
}

当状态变量 count 改变时,查询到只有 Button 组件关联了它;执行 Button 组件的更新方法,实现按需刷新。

运行效果:
在这里插入图片描述

装饰 class 对象类型的变量

@State 除了可以装饰简单类型的变量以外,好可以装修 class 对象,比如,我们在代码里面定义一个 class 类:

class Model {public value: string;constructor(value: string) {this.value = value;}
}@Entry
@Component
struct Index {@State count: number = 0;build() {Column() {MyComponent({ count: 1, increaseBy: 2 }).margin(20)MyComponent({ title: new Model('Hello, World 2'), count: 7 }).margin(40)}.width('100%')}
}@Component
struct MyComponent {@State title: Model = new Model('Hello World');@State count: number = 0;private increaseBy: number = 1;build() {Column() {Text(`${this.title.value}`)Button(`Click to change title`).onClick(() => {// @State 变量的更新将触发上面的 Text 组件内容更新this.title.value = this.title.value === 'Hello ArkUI' ? 'Hello World' : 'Hello ArkUI';}).margin(20)Button(`Click to increase count=${this.count}`).onClick(() => {// @State 变量的更新将触发该 Button 组件的内容更新this.count += this.increaseBy;})}}
}

运行效果:

在这里插入图片描述

可以发现:第一个 MyComponent 内部状态的更改是不会影响第二个 MyComponent。

@Prop 装饰器使用规则

@Prop变量装饰器说明
装饰器参数
同步类型单向同步:对父组件状态变量值的修改,将同步给子组件@Prop装饰的变量,
子组件@Prop变量的修改不会同步到父组件的状态变量上
允许装饰的变量类型string、number、boolean、enum 类型。
不支持 any,不允许使用 undefined 和 null。
必须指定类型。
在父组件中,传递给 @Prop 装饰的值不能为 undefined 或者 null
被装饰变量的初始值允许本地初始化

要理解 @Prop 变量值初始化和更新机制,有必要了解父组件和拥有 @Prop 变量的子组件初始渲染和更新流程。

1. 初始渲染:

⇒ 执行父组件的 build() 函数将创建子组件的新实例,将数据源传递给子组件;

⇒ 初始化子组件 @Prop 装饰的变量。

2. 更新:

⇒ 子组件 @Prop 更新时,更新仅停留在当前子组件,不会同步回父组件;

⇒ 当父组件的数据源更新时,子组件的 @Prop 装饰的变量将被来自父组件的数据源重置,所有 @Prop 装饰的本地的修改将被父组件的更新覆盖。

@Prop 装饰器使用场景

父组件 @State 到子组件 @Prop 简单数据类型同步

@Entry
@Component
struct Index {@State parentValue: number = 10;build() {Column() {Text(`父组件传递数据 ${this.parentValue}`).margin(20)// 父组件的数据源的修改会同步给子组件Button(`加一个 + 1`).onClick(() => {this.parentValue += 1;})// 父组件的修改会同步给子组件Button(`减一个 -1`).onClick(() => {this.parentValue -= 1;}).margin(20)CountDownComponent({ subValue: this.parentValue, costOfOneAttempt: 2 })}.width('100%')}
}@Component
struct CountDownComponent {@Prop subValue: number;costOfOneAttempt: number = 1;build() {Column() {if (this.subValue > 0) {Text(`子组件获取到的数据: ${this.subValue}`).margin({ top: 50 })} else {Text('Over!').margin({ top: 50 })}// @Prop 装饰的变量不会同步给父组件Button(`子组件内部操作`).onClick(() => {this.subValue -= this.costOfOneAttempt;}).margin(20)}}
}

运行效果:

在这里插入图片描述

解读下代码:

  1. CountDownComponent 子组件首次创建时其 @Prop 装饰的 subValue 变量将从父组件 @State 装饰的 parentValue 变量初始化;
  2. 按“+1”或“-1”按钮时,父组件的 @State 装饰的 parentValue 值会变化,这将触发父组件重新渲染,在父组件重新渲染过程中会刷新使用 parentValue 状态变量的 UI 组件并单向同步更新 CountDownComponent 子组件中的 subValue 值;
  3. 更新 subValue 状态变量值也会触发 CountDownComponent 的重新渲染,在重新渲染过程中,评估使用 subValue 状态变量的 if 语句条件(this.subValue > 0),并执行 true 分支中的使用 subValue 状态变量的 UI 组件相关描述来更新 Text 组件的 UI 显示;
  4. 当按下子组件 CountDownComponent 的“子组件内部操作”按钮时,其 @Prop 变量 subValue 将被更改,但是 subValue 值的更改不会影响父组件的 parentValue 值;
  5. 父组件的 parentValue 值会变化时,父组件的修改将覆盖掉子组件 CountDownComponent 中 subValue 本地的修改。

父组件 @State 数组项到子组件 @Prop 简单数据类型同步

@Entry
@Component
struct Index {@State arr: number[] = [1,2,3];build() {Column() {Child({value: this.arr[0]})Child({value: this.arr[1]})Child({value: this.arr[2]})Divider().height(5)ForEach(this.arr,item => {Child({value: item})},item => item.toString())Divider().height(5)Text('replace entire arr').fontSize(40).onClick(()=>{// 两个数组都包含项“3”。this.arr = this.arr[0] == 1 ? [3,4,5] : [1,2,3];})}.width('100%')}
}@Component
struct Child {@Prop value: number;build() {Text(`${this.value}`).fontSize(50).onClick(()=>{this.value++})}
}

看下效果:

在这里插入图片描述

页面显示正常,接下来有意思的事情就发生了,我们看下实操效果:

在这里插入图片描述

方便你看到前后效果,我们对比下前后的结果图:

在这里插入图片描述

让我们来分析下这个数值变化:

  1. 在子组件 Child 中做的所有的修改都不会同步回父组件 Index 组件,所以即使 6 个组件显示都为 6,但在父组件 Index 中,this.arr 保存的值依旧是 [1,2,3]。
  2. 点击 replace entire arr,this.arr[0] == 1 成立,将 this.arr 赋值为 [3, 4, 5];
  3. 因为 this.arr[0] 已更改,Child({value: this.arr[0]}) 组件将 this.arr[0] 更新同步到实例 @Prop 装饰的变量。Child({value: this.arr[1]}) 和 Child({value: this.arr[2]}) 同理。
  4. this.arr 的更改触发 ForEach 更新,this.arr 更新的前后都有数值为 3 的数组项:[3, 4, 5] 和[1, 2, 3]。根据 diff 算法,数组项“3”将被保留,删除“1”和“2”的数组项,添加为“4”和“5”的数组项(关于 ForEach 的这个特性,不懂的可以查看 写给初学者的 HarmonyOS 教程 – 循环渲染(ForEach))。这就意味着,数组项“3”的组件不会重新生成,而是将其移动到第一位。所以“3”对应的组件不会更新,此时“3”对应的组件数值为“6”,ForEach最终的渲染结果是“6”,“4”,“5”。

从父组件中的 @State 类对象属性到 @Prop 简单类型的同步

class Book {public title: string;public pages: number;public readIt: boolean = false;constructor(title: string, pages: number) {this.title = title;this.pages = pages;}
}@Component
struct ReaderComp {@Prop title: string;@Prop readIt: boolean;build() {Row() {Text(this.title)Text(`... ${this.readIt ? '已阅读' : '还没读'}`).onClick(() => this.readIt = true)}}
}@Entry
@Component
struct Index {@State book: Book = new Book('《 肖生克的救赎 》', 765);build() {Column() {ReaderComp({ title: this.book.title, readIt: this.book.readIt }).margin(20)ReaderComp({ title: this.book.title, readIt: this.book.readIt }).margin(20)ReaderComp({ title: this.book.title, readIt: this.book.readIt }).margin(20)ReaderComp({ title: this.book.title, readIt: this.book.readIt }).margin(20)}.width('100%')}
}

代码很简单,不用过多解释,直接看效果:

在这里插入图片描述

@Prop 本地初始化不和父组件同步

为了支持 @Component 装饰的组件复用场景,@Prop 支持本地初始化,这样可以让 @Prop 是否与父组件建立同步关系变得可选。当且仅当 @Prop 有本地初始化时,从父组件向子组件传递 @Prop 的数据源才是可选的。

下面的示例中,子组件包含两个 @Prop 变量:

  1. @Prop customCounter 没有本地初始化,所以需要父组件提供数据源去初始化 @Prop,并当父组件的数据源变化时,@Prop 也将被更新;
  2. @Prop customCounter2 有本地初始化,在这种情况下,@Prop 依旧允许但非强制父组件同步数据源给 @Prop。
@Component
struct MyComponent {@Prop customCounter: number;@Prop customCounter2: number = 5;build() {Column() {Row() {Text(`From Main: ${this.customCounter}`).width(90).height(40).fontColor('#FF0010')}Row() {Button('子组件本地修改 !').width(180).height(60).margin({ top: 10 }).onClick(() => {this.customCounter2++})}.height(100).width(180)Row() {Text(`Custom Local: ${this.customCounter2}`).width(90).height(40).fontColor('#FF0010')}}}
}@Entry
@Component
struct Index {@State mainCounter: number = 10;build() {Column() {Row() {Column() {Button('修改数值').width(480).height(60).margin({ top: 10, bottom: 10 }).onClick(() => {this.mainCounter++})}}Row() {// customCounter 必须从父组件初始化,因为 MyComponent 的 customCounter 成员变量缺少本地初始化;此处,customCounter2 可以不做初始化。MyComponent({ customCounter: this.mainCounter })// customCounter2 也可以从父组件初始化,父组件初始化的值会覆盖子组件 customCounter2 的本地初始化的值MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter })}}}
}

效果:

在这里插入图片描述

@Link 装饰器使用规则

@Link变量装饰器说明
装饰器参数
同步类型双向同步。
父组件中 @State/@StorageLink/@Link 和子组件 @Link 可以建立双向数据同步,反之亦然。
允许装饰的变量类型Object、class、string、number、boolean、enum 类型,以及这些类型的数组。
类型必须被指定,且和双向绑定状态变量的类型相同。
不支持 any,不支持简单类型和复杂类型的联合类型,不允许使用 undefined 和 null。
被装饰变量的初始值无,禁止本地初始化。

为了了解 @Link 变量初始化和更新机制,有必要先了解父组件和拥有 @Link 变量的子组件的关系,初始渲染和双向更新的流程(以父组件为 @State 为例)。

1. 初始渲染:

执行父组件的 build() 函数后将创建子组件的新实例。初始化过程如下:

⇒ 必须指定父组件中的 @State 变量,用于初始化子组件的 @Link 变量。子组件的 @Link 变量值与其父组件的数据源变量保持同步(双向数据同步)。

⇒ 父组件的 @State 状态变量包装类通过构造函数传给子组件,子组件的 @Link 包装类拿到父组件的 @State 的状态变量后,将当前 @Link 包装类 this 指针注册给父组件的 @State 变量。

2. @Link 的数据源更新:

即父组件中状态变量更新,引起相关子组件的 @Link 的更新,处理步骤:

⇒ 通过初始渲染的步骤可知,子组件 @Link 包装类把当前 this 指针注册给父组件。父组件 @State 变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(比如 @Link 包装类)。

⇒ 通知 @Link 包装类更新后,子组件中所有依赖 @Link 状态变量的系统组件(elementId)都会被通知更新。以此实现父组件对子组件的状态数据同步。

3. @Link的更新:

当子组件中 @Link 更新后,处理步骤如下(以父组件为 @State 为例):

⇒ @Link 更新后,调用父组件的 @State 包装类的 set 方法,将更新后的数值同步回父组件。

⇒ 子组件 @Link 和父组件 @State 分别遍历依赖的系统组件,进行对应的 UI 的更新。以此实现子组件 @Link 同步回父组件 @State。

@Link 装饰器使用场景

简单类型和类对象类型的 @Link

以下示例中,点击父组件 ShufflingContainer 中的 “父组件: 设置红色 Button” 和 “父组件: 设置蓝色 Button”,可以从父组件将变化同步给子组件,子组件 GreenButton 和 RedButton 中 @Link 装饰变量的变化也会同步给其父组件。

class BlueButtonState {width: number = 0;constructor(width: number) {this.width = width;}
}@Component
struct BlueButton {@Link blueButtonState: BlueButtonState;build() {Button('Blue Button').width(this.blueButtonState.width).height(120.0).backgroundColor('#085DFF').onClick(() => {if (this.blueButtonState.width < 400) {// 更新 class 的属性,变化可以被观察到同步回父组件this.blueButtonState.width += 50;} else {// 更新 class 的属性,变化可以被观察到同步回父组件this.blueButtonState = new BlueButtonState(200);}})}
}
@Component
struct RedButton {@Link redButtonState: number;build() {Button('Red Button').width(this.redButtonState).height(150.0).backgroundColor('#ff0000').onClick(() => {// 子组件的简单类型可以同步回父组件this.redButtonState += 20.0;})}
}
@Entry
@Component
struct ShufflingContainer {@State blueButtonState: BlueButtonState = new BlueButtonState(200);@State redButtonProp: number = 100;build() {Column() {// class 类型从父组件 @State 向子组件 @Link 数据同步Button('父组件: 设置绿色 Button').onClick(() => {this.blueButtonState.width = (this.blueButtonState.width < 400) ? this.blueButtonState.width + 100 : 100;}).margin(20)// class 类型初始化 @LinkBlueButton({ blueButtonState: $blueButtonState })Divider().height(20)// 简单类型从父组件 @State 向子组件 @Link 数据同步Button('父组件: 设置红色 Button').onClick(() => {this.redButtonProp = (this.redButtonProp < 400) ? this.redButtonProp + 100 : 100;}).margin(20)// 简单类型初始化 @LinkRedButton({ redButtonState: $redButtonProp })}.width('100%').justifyContent(FlexAlign.Center)}
}

运行效果:

在这里插入图片描述

数组类型的@Link

@Component
struct Child {@Link items: number[];build() {Column() {Button(`Button1: push`).onClick(() => {this.items.push(this.items.length + 1);}).margin(20)Button(`Button2: replace whole item`).onClick(() => {this.items = [100, 200, 300];})}}
}@Entry
@Component
struct Parent {@State arr: number[] = [1, 2, 3];build() {Column() {Child({ items: $arr })Divider().height(20)ForEach(this.arr,item => {Text(`${item}`)},item => item.toString())}.width('100%')}
}

运行效果:

在这里插入图片描述

ArkUI 框架可以观察到数组元素的添加,删除和替换。在该示例中 @State 和 @Link 的类型是相同的 number[],不允许将@Link 定义成 number 类型(@Link item : number),并在父组件中用 @State 数组中每个数据项创建子组件。

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

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

相关文章

深度学习在计算机视觉中的应用

深度学习在计算机视觉中的应用 摘要&#xff1a;本文介绍了深度学习在计算机视觉领域的应用&#xff0c;包括目标检测、图像分类、人脸识别等。通过分析深度学习在计算机视觉中的实际应用案例&#xff0c;阐述了深度学习在计算机视觉中的优势和未来发展趋势。 一、引言 计算…

使用rust slint开发桌面应用

安装QT5&#xff0c;过程省略 安装rust&#xff0c;过程省略 创建工程 cargo new slint_demo 在cargo.toml添加依赖 [dependencies] slint "1.1.1" [build-dependencies] slint-build "1.1.1" 创建build.rs fn main() {slint_build::compile(&quo…

8.HTTP工作原理

HTTP是什么 HTTP工作原理 HTTP协议的请求类型和响应状态码 总结 1.HTTP是什么 HTTP超文本传输协议就是在一个网络中上传下载文件的一套规则 2.HTTP工作原理 HTTP超文本传输协议的本质是TCP通信&#xff0c;链接—>请求—>响应—>断开 3.HTTP协议的请求类型和响应状…

Java+Swing+Mysql实现超市管理系统

一、系统介绍 1.开发环境 操作系统&#xff1a;Win10 开发工具 &#xff1a;IDEA2018 JDK版本&#xff1a;jdk1.8 数据库&#xff1a;Mysql8.0 2.技术选型 JavaSwingMysql 3.功能模块 4.系统功能 1.系统登录登出 管理员可以登录、退出系统 2.商品信息管理 管理员可以对商品信息…

Android画布Canvas绘制drawBitmap基于源Rect和目的Rect,Kotlin

Android画布Canvas绘制drawBitmap基于源Rect和目的Rect&#xff0c;Kotlin <?xml version"1.0" encoding"utf-8"?> <androidx.appcompat.widget.LinearLayoutCompat xmlns:android"http://schemas.android.com/apk/res/android"xmlns…

Cannot find module ‘node:url‘报错处理

在运行vite搭建的项目时&#xff0c;遇到Cannot find module node:url’报错。具体错误如图所示&#xff1a; 造成以上问题的原因是node版本较低。Vite 需要 Node.js 版本 14.18&#xff0c;16。 解决方案&#xff1a; 上面是通过nvm切换高版本node。 再次执行运行命令&…

基于Springboot的社区医院管理服务系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的社区医院管理服务系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…

C语言--每日选择题--Day36

第一题 1. 以下关于指针的说法,正确的是() A&#xff1a;int *const p 与 int const *p等价 B&#xff1a;const int *p 与 int *const p等价 C&#xff1a;const int *p 与 int const *p 等价 D&#xff1a;int *p[10] 与 int (*p)[10] 等价 答案及解析 C const 在*的左侧&…

缓存穿透、击穿、雪崩

缓存穿透&#xff1a; 指的是恶意用户或攻击者通过请求不存在于缓存和后端存储中的数据来使得所有请求都落到后端存储上&#xff0c;导致系统瘫痪。 解决方案&#xff1a; 通常包括使用布隆过滤器或者黑白名单等方式来过滤掉无效请求&#xff0c;以及在应用程序中加入缓存预热…

SpringSecurity6 | 默认用户生成

SpringSecurity6 | 默认用户生成 ✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Java…

从0开始学Spring、Springboot总结笔记(持续更新中~)

文章目录 一.基于SpringBoot进行Web开发入门1.IDEA编译器中创建springboot工程扩展&#xff1a;如何解决pom.xml文件中“找不到Maven插件”的问题&#xff1f; 2.Springboot项目如何编写请求类和请求方法并启动访问编写请求类和请求方法启动Springboot访问 一些学习资源参考 一…

如何搭建eureka-server

在Spring Cloud项目的pom文件中添加eureka-server的starter依赖坐标 <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.0 http://ma…

人工智能学习4(特征选择)

编译工具&#xff1a;PyCharm 有些编译工具在绘图的时候不需要写plt.show()或者是print就可以显示绘图结果或者是显示打印结果&#xff0c;pycharm需要&#xff08;matplotlib.pyplot&#xff09; 文章目录 编译工具&#xff1a;PyCharm 特征选择嵌入法特征选择练习&#xff…

云原生的 CI/CD 框架tekton - Trigger(二)

上一篇为大家详细介绍了tekton - pipeline&#xff0c;由于里面涉及到的概念比较多&#xff0c;因此需要好好消化下。同样&#xff0c;今天在特别为大家分享下tekton - Trigger以及案例演示&#xff0c;希望可以给大家提供一种思路哈。 文章目录 1. Tekton Trigger2. 工作流程3…

Linux高级系统编程中的系统调用

概念 是操作系统提供给用户使其可以操作内核提供服务的一组函数接口。 用户态和内核态&#xff1a; 引入 &#xff1a; 整个 计算机系统 的。好比你写 一个程序&#xff0c;但是因为你对 硬件操作 不熟悉&#xff0c;出现 问题&#xff0c;那么影响范围是多大&#xff1f;是整…

数据结构(超详细讲解!!)第二十六节 图(中)

1.存储结构 1.邻接矩阵 图的邻接矩阵表示法&#xff08;Adjacency Matrix&#xff09;也称作数组表示法。它采用两个数组来表示图&#xff1a; 一个是用于存储顶点信息的一维数组&#xff1b;另一个是用于存储图中顶点之间关联关系的二维数组&#xff0c;这个关联关系数组被…

http面试题,三次握手四次挥手

在浏览器中输入网址按下回车经历了一个怎样的过程&#xff1f; 总的来说分为以下几个过程&#xff1a; 1、DNS解析&#xff1a;将域名解析为IP地址; 2、TCP连接&#xff1a;TCP三次握手; 3、发生HTTP请求; 4、服务器处理请求并返回HTTP报文; 5、浏览器解析渲染页面; 6、断开连接…

Isaac Sim教程03 Isaac Sim的基本使用

Isaac Sim 基本使用 版权信息 Copyright 2023 Herman YeAuromix. All rights reserved.This course and all of its associated content, including but not limited to text, images, videos, and any other materials, are protected by copyright law. The author holds…

CentOS服务自启权威指南:手动启动变为开机自启动(以Jenkins服务为例)

前言 CentOS系统提供了多种配置服务开机自启动的方式。本文将介绍其中两种常见的方式&#xff0c; 一种是使用Systemd服务管理器配置&#xff0c;不过&#xff0c;在实际中&#xff0c;如果你已经通过包管理工具安装的&#xff0c;那么服务通常已经被配置为Systemd服务&#…

渗透测试学习day7

文章目录 靶机&#xff1a;VaccineTask1Task2Task3Task4Task5Task6 7-9解题过程Task7Submit user flagSubmit root flag 靶机&#xff1a;Vaccine Task1 问题&#xff1a;除了SSH和HTTP&#xff0c;这个盒子上还托管了什么服务&#xff1f; ftpnmap扫一下 Task2 问题&…