写给初学者的 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;阐述了深度学习在计算机视觉中的优势和未来发展趋势。 一、引言 计算…

学习SpringCloud

JWT JWT&#xff08;JSON Web Token&#xff09;是一种用于在网络应用间传递信息的安全标准。JwtTool生产成 三个部分组成&#xff1a;头部&#xff08;Header&#xff09;、载荷&#xff08;Payload&#xff09;和签名&#xff08;Signature&#xff09;。 工作流程如下&…

更新 Node.js 和 npm 使用 nvm

更新 Node.js 和 npm 使用 nvm 问题: 当前 Node.js 版本为 16.20.2&#xff0c;不兼容所需的 npm 版本 10.2.4。需要的 npm 版本 10.2.4 要求 Node.js 版本至少为 18.17.0 或更高。 解决步骤: 查看可用的 Node.js 版本 使用 nvm list available 命令来查看所有可安装的 Node…

【C++ protobuf中对不同消息内容进行赋值的方式】

本文中用到的消息结构&#xff1a; message PointNameInfo {optional double longitude 1;// 经度坐标optional double latitude 2;// 纬度坐标optional uint64 timestamp_sec 3;// 时间戳optional uint32 scale 4;optional string name 5; }message PointInfo {optional…

C++之vector插入初始化性能优化

C整理集合C刷题基础知识&#xff08;栈、队列、hash、STL、基础函数等&#xff09;---持续更新-CSDN博客 vector的高级使用&#xff08;优化&#xff09; 1、元素插入&#xff08;优化&#xff09; 对于vector的元素插入存在两个函数push_back()以及emplace_back()&#xff…

记录 | CUDA编程中用constexpr替代__host____device__

比如用 __host__ & __device__ 的情况如下&#xff1a; #include <cstdio> #include <cuda_runtime.h>__host__ __device__ void say_hello(){printf("Hello, world!\n"); }__global__ void kernel(){say_hello(); }int main(){kernel<<<1…

使用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协议的请求类型和响应状…

qt/c/c++文件操作总结

1. 读取文件 1.1 Qt以二进制方式读取大文件返回char* 在Qt中以二进制模式读取一个大文件(以500MB为例)并将其内容存储到char*数组中,需要谨慎处理内存分配。以下是实现这一功能的步骤和示例代码: 1. 打开文件 使用QFile类以二进制模式打开文件。 2. 检查文件大小 使用…

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;三层体系…

如何保障Redis的安全性?

身份验证和访问控制&#xff1a; 认证密码&#xff08;requirepass&#xff09;&#xff1a; 在Redis配置文件中设置 requirepass 参数&#xff0c;要求客户端连接时提供密码。确保密码的复杂度&#xff0c;定期更新密码&#xff0c;以防泄漏。网络绑定&#xff08;bind&#x…

QLineEdit 的 InputMask掩码

QLineEdit 的 InputMask掩码 A&#xff1a;只能输入字母&#xff0c;且不可省略 a&#xff1a;只能输入字母&#xff0c;可以省略 N&#xff1a;只能输入 字母和数字&#xff0c;且不可省略 n&#xff1a;只能输入 字母和数字&#xff0c;可以省略 X&#xff1a;可以输入任意字…

如何写好一篇硬件经验总结文档

大家好,这里是大话硬件。 今天这篇文章想分享一个工作方法,主要用在如何写好一篇硬件问题总结文档上。 我们在工作中不可避免会碰到一些复杂的硬件问题,这些问题可能出现在项目研发过程中,也可能来自客户的反馈。 当困扰大家很久的棘手问题被解决完后,如果被总结成一篇…

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 在*的左侧&…

代码随想录 509. 斐波那契数

题目 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0&#xff0c;F(1) 1 F(n) F(n - 1) F(n - 2)&#xff0c;其中 n > 1 给定…

后端架构的一些知识

目录 一.抖音 二.大型网站是如何管理海量的数据的 三.大型网站停机一天会造成多大损失 四.如何设计一套安全&#xff0c;健壮&#xff0c;可扩展&#xff0c;稳定性强的后端系统 五.如何在不影响原来代码的基础上进行功能更新 六.大型网站一年都不停机吗 七.线上业务出现…

缓存穿透、击穿、雪崩

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