组件和状态管理
组件概述
组件
是设备绘制在屏幕上的一个界面元素或对象,它是组成用户界面的基本单位。
管理组件
的组件称为容器组件
,容器组件
既可以容纳普通组件
,也可以容纳别的容器组件
。容器组件
又被称为布局
。
HarmonyOS提供了丰富的组件和容器组件
,如Text
、Button
、Image
、List
、Tabs
等。
实例化组件的一般方式如下。
组件名([参数]) {
// 子组件
}
// 链式调用组件属性
.property1()
.property2()
.property3()......
组件属性
组件属性一般分为通用属性
和自定义属性
。
通用属性
包括尺寸
、位置
、背景
、文本样式
等。
自定义属性
一般会是这样的形式。
// 组件装饰器
@Component
// 使用struct关键字定义结构体
struct CustomerProperty {
variable: number = 0;
build() {
......
}
}
一个完整的使用自定义组件并初始化的代码。
@Entry
@Component
struct Index {
build() {
Column() {
// 自定义无参数实例化
MyComponent()
// 设置通用属性
.margin(10);
// 自定义组件带参数实例化
MyComponent({count: 100})
// 设置背景
.backgroundColor(0xFFFFFF);
}
// 系统组件带参数构建
.size({width: '100%', height: '100%'});
}
}
@Component
struct MyComponent {
// 无装饰器的常规成员变量,初始化为0
variable: number = 0;
build() {
if (this.variable == 0) {
Text('无内容可显示').fontSize(22);
} else {
Text('当前count=' + this.variable).fontSize(22);
}
}
}
没有装饰器修饰的属性,既可以使用
本地初始化
方式,也可以采用构造参数初始化
的方式。使用装饰器修饰的属性,初始化时就有一定的限制,有的属性只能进行
本地初始化,而有的只能使用
构造参数初始化。
属性装饰器 | 本地初始化限制 | 构造参数初始化限制 |
---|---|---|
@State | 必须进行本地初始化 | 可选 |
@Prop | 禁止进行本地初始化 | 必须进行本地初始化 |
@StorageProp | 必须进行本地初始化 | 禁止进行本地初始化 |
@Link | 禁止进行本地初始化 | 必须进行本地初始化 |
@StorageLink | 必须进行本地初始化 | 禁止进行本地初始化 |
@Provide | 必须进行本地初始化 | 可选 |
@Consume | 禁止进行本地初始化 | 禁止进行本地初始化 |
@ObjectLink | 禁止进行本地初始化 | 必须进行本地初始化 |
常规成员变量 | 可选 | 可选 |
上面表格中修饰器的代码实例展示如下。
@Component
struct CustomerProperty {
// 无装饰器的变量,本地初始化
// 另一种初始化方式是在构建组件时进行初始化,例如,MyComponent({count: 0})
count: number = 0;
// 下面都是有装饰器修饰的自定义属性
@State mystate: 类型 = 初始值; // 必须进行本地初始化
@Prop myprop: 类型; // 禁止进行本地初始化
@StorageProp mystorageprop: 类型 = 初始值; // 必须进行本地初始化
@Link mylink: 类型; // 禁止进行本地初始化
@StorageLink mystoragelink: 类型 = 初始值; // 必须进行本地初始化
@Provide myprovide: 类型 = 初始值; // 必须进行本地初始化
@Consume myconsume: 类型; // 禁止进行本地初始化
@ObjectLink myobjectlink: 类型; // 禁止进行本地初始化
build() {
......
}
}
放组件存在父子关系
(指的是一个组件包含
了另一个组件而非继承
)时,组件的赋值属性存在限制。
@Entry
@Component
// 父组件定义
struct Parent {
// 如果这里加了@State就不能用它来初始化子组件
flag: boolean = true;
build() {
Column() {
// 子组件实例
Child();
// 子组件实例,传递参数
Child({childState: this.flag});
}.width("100%")
}
}
@Component
// 子组件定义
struct Child {
@State childState: boolean = false;
build() {
Row() {
Text('子组件状态' + this.childState).fontSize(30);
}
}
}
父子组件的初始化限制说明如下。
装饰器 | 用途说明 |
---|---|
@State | 不允许使用父组件中@State 、@Link 和@Prop 装饰的属性作为初始化参数允许使用常规变量作为初始化参数 |
@Link | 不允许使用父组件中@Prop 、@StorageProp 装饰的属性和常规变量作为初始化参数允许使用 @State 、@Link 和@StorageLink 装饰的属性作为初始化参数 |
@Prop | 不允许使用父组件中@StorageLink 、@StorageProp 装饰的属性和常规变量作为初始化参数允许使用 @State 、@Link 和@Prop 装饰的属性作为初始化参数 |
常规成员变量 | 允许使用任意装饰的属性和常规变量作为初始化参数 |
组件事件
和以数据为参数
的组件属性不同,组件事件是以函数为参数
的。
有三种配置组件事件的方式。
使用
Lambda表达式
。使用组件的
成员函数
。使用
匿名函数
表达式。
@Entry
@Component
struct ComponentA {
@State count: number = 0;
// 定义成员函数
fun(): void {
console.log("do something");
this.count++;
}
build() {
Column() {
// Lambda表达式方式
Button('点击1')
.onClick(() => {
this.count++
});
// 成员函数方式,不绑定不能访问this.count
Button('点击2').onClick(this.fun.bind(this));
// 匿名函数方式
Button('点击3')
.onClick(function() {
this.count++;
console.log("do something");
}
.bind(this));
Text(`count:${this.count}`).fontSize(22);
}
.width('100%');
}
}
组件状态
状态模型
在基于ArkTS的声明式UI编程范式
中,UI
是应用程序状态的函数。也就是说,当应用程序状态发生改变时,系统就会自动更新UI界面
。
对于没有数据存储的UI界面
,其中的组件可以通过状态装饰器装饰的成员变量来关联组件,并且自动更新相关联的状态组件。
组件之间状态的更新关系如下图所示。

当父组件用
@State
装饰器装饰的属性更新时,会自动更新子组件中用@Prop
装饰器装饰的属性(不过这种更新是单向
的)。也有
双向
的,就像上面图中右边部分显示的那样。
对于具有应用存储(AppStorage)
能力的应用来说,组件和AppStorage
之间可以建立双向或单向的数据更新机制。

总体而言,HarmonyOS中常见的几种装饰器
都在官方给出的这张图里了。

在它们中用得较多是@State
、@Prop
和@Link
。
@State状态
由@State
装饰的属性是组件内部的状态数据。当此属性变化时,会自动调用所在组件的build()
方法刷新界面。
@Entry
@Component
struct StateTest {
@State
count: number = 0;
build() {
Column() {
Text(`刷新点击次数: ${this.count}`).fontSize(30);
// 无参数实例化
MyButton();
// 带参数初始化
MyButton({isclicked: false});
}
.width("100%")
.backgroundColor(0xff0000)
.padding(20)
.onClick(() => {
this.count++;
});
}
}
@Component
struct MyButton {
@State
isclicked: boolean = true;
build() {
Column() {
Button() {
Text(`当前状态: ${this.isclicked}`).fontSize(22);
}
.margin(10)
.onClick(() => {
this.isclicked = !this.isclicked;
});
}
}
}
@State
可以class
、number
、string
、boolean
类型的变量,也可以装饰这些类型构成的泛型数组,但不能装饰object
和any
类型的变量。@State
只能监听到第一层状态数据
(也就是struct或自定义组件的直接成员变量)的改变,内层数据的改变无法触发build()
的执行。当组件有多个实例时,每个实例内部的状态数据是独立的。
之前说过,由
@State
装饰的变量必须进行本地初始化,但自定义组件的内部状态变量可以通过构造参数进行初始化。
@Prop状态
@Prop
与@State
有相同的语义,但初始化方式不同。
由@Prop
装饰的变量在与其他组件由@State
装饰的变量之间存在父子关系
(@State
是父,@Prop
为子)的绑定。
当父组件的@State
变量发生变化时会通知子组件中的@Prop
变量。但反过来,@Prop
变量发生变化时却不会通知@State
变量。
@Entry
@Component
struct ParentComponent {
@State goldCount: number = 10;
build() {
Column() {
Row() {
Text(`初始化金币数量:${this.goldCount} `)
.margin(10).fontSize(15);
Button() {
Text('- 1');
}
.margin(5)
.size({height: 30, width: 30})
.backgroundColor(0xccccff)
.onClick(() => {
this.goldCount -= 1;
})
Button() {
Text(' + 1 ');
}
.margin(5)
.size({ height: 30, width: 30 })
.backgroundColor(0xccccff)
.onClick(() => {
this.goldCount += 1;
})
}
.margin(10);
// 创建三个子组件,必须初始化@Prop变量count,普通变量可以不通过参数初始化
// 已绑定
ChildComponent({count: this.goldCount});
// 已绑定
ChildComponent({count: this.goldCount, cost: 2});
// 无绑定
ChildComponent({count: 100, cost: 5});
}
.backgroundColor(0xeeeeee)
.width('100%');
}
}
@Component
struct ChildComponent {
// @Prop装饰的变量
@Prop count: number;
// 普通变量
private cost: number = 1;
build() {
Row() {
if (this.count > 0) {
Text(` 您剩余金币:${this.count} 个`);
} else {
Text('已用完!');
}
Button() {
Text(`点击消费 ${this.cost} 金币`);
}
.padding(10)
.onClick(() => {
this.count -= this.cost;
});
}
.backgroundColor(0xbbbbbb)
.margin(5)
.padding(10);
}
}
@Prop
只能装饰number
、string
、boolean
等简单类型。不能在组件内初始化
@Prop
变量,而只能通过构造参数进行初始化。
@Link状态
由@Link
装饰的变量可以和父组件的@State
变量之间建立双向数据绑定关系,这是和@Prop
最主要的不同。
@Entry
@Component
struct ParentComponent {
// 由@State装饰的变量
@State message: string = '';
@State curValue: string = '';
build() {
Column() {
Text(` ${this.message} `)
.margin(5)
.fontSize(18)
.fontColor(0xff0000);
// 创建子组件必须初始化@Link变量,普通变量通过参数初始化
// 通过`$`操作符创建引用:将msg和message绑定,value和curValue绑定
ChildComponent({ msg: $message, value: $curValue, hint: '输入用户名' });
Text(`当前值:${this.curValue} `)
.margin(5)
.fontSize(18)
.fontColor(0x00ff00);
Button() {
Text(` 重 置 `).fontSize(22);
}.onClick(() => {
this.message = '您点击了重置';
// 这里会更新绑定的子组件变量value
this.curValue = '';
})
}
.backgroundColor(0xeeeeee)
.width('100%')
.padding(30);
}
}
@Component
struct ChildComponent {
// 由@Link装饰的变量
@Link msg: string;
@Link value: string;
minLength: number = 8;
hint: string = ';
build() {
Row() {
TextInput({ text: this.value, placeholder: this.hint })
.fontSize(30)
.onChange((v: string) => {
this.value = v;
if (v.length < this.minLength) {
// msg更新时会更新父组件的message变量
this.msg = '当前输入的长度为 ' + v.length + '小于' + this.minLength;
} else {
this.msg = '符合长度要求';
}
});
}
.backgroundColor(0xbbbbbb)
.margin(5)
.padding(10);
}
}
@Link
可装饰的变量类型与@State
相同。@Link
变量不能执行本地初始化。自定义组件在实例化时,必须同时初始化所有
@Link
变量。由
@Link
装饰的变量可以执行多层嵌套的初始化。初始化后的
@State
变量,可以通过$
创建引用。
AppStorage
@State
、@Prop
和@Link
等装饰器比较适合于单个页面中父子组件之间的互动,对于多个页面间的数据共享一般使用应用存储(AppStorage)更合适。
AppStorage是HarmonyOS应用程序中的单例对象,跟随ArkUI启动,在应用退出时销毁,为应用程序范围内的可变状态属性提供一致性存储。
组件可以通过装饰器与AppStorage进行同步,业务逻辑也可以通过接口访问AppStorage中存储的数据。
组件通过使用@StorageLink(key)
装饰变量,与AppStorage实现双向绑定。
组件通过使用@StorageProp(key)
装饰变量,与AppStorage实现单向绑定。
index.ets
页面代码。
// index.ets页面
import router from '@ohos.router';
@Entry
@Component
struct Index {
// 注意下面两个装饰符都是@StorageLink双向绑定
@StorageLink('count1') indexCount1: number = 0;
@StorageLink('count2') indexCount2: number = 0;
private label: string = '点击';
build() {
Column({ space: 20 }) {
Text(`当前页面是 Index.ets`).fontSize(30);
Button(`${this.label} `)
.onClick(() => {
// 下面是通过API修改应用存储变量
var temp = AppStorage.Get<number>('count1');
AppStorage.Set('count1', temp + 1);
// 下面通过组件成员变量修改
this.indexCount2++;
});
// 使用组件的成员变量
Text(`indexCount1:${this.indexCount1}`).fontSize(22);
Text(`indexCount2:${this.indexCount2}`).fontSize(22);
// 使用存储变量
Text(`count1:${AppStorage.Get<number>('count1').toString()}`)
.fontSize(22);
Text(`count2:${AppStorage.Get<number>('count2').toString()}`)
.fontSize(22);
Button('跳转到next页面')
.onClick(() => {
// 跳转到next.ets
router.pushUrl({ url: 'pages/Next' });
})
}
.width('100%')
.padding(20)
.backgroundColor(0xccFFcc);
}
}
next.ets
页面代码。
// next.ets页面
import router from '@ohos.router';
@Entry
@Component
struct Next {
// 注意下面两个装饰符都是@StorageProp单向绑定
@StorageProp('count1') nextCount1: number = 0;
@StorageProp('count2') nextCount2: number = 0;
private label: string = '点击';
build() {
Column({ space: 20 }) {
Text(`当前页面是 Next.ets`).fontSize(30)
Button(`${this.label} `)
.onClick(() => {
// 可以通过API修改应用存储变量
var t = AppStorage.Get<number>('count1');
AppStorage.Set<number>('count1', t+1);
// 下面修改提示错误TypeError: no setter for property
this.nextCount2++;
});
// 使用组件的成员变量
Text(`nextCount1:${this.nextCount1}`).fontSize(22);
Text(`nextCount2:${this.nextCount2}`).fontSize(22);
// 通过API使用存储变量
Text(`count1:${AppStorage.Get<number>('count1').toString()} `)
.fontSize(22);
Text(`count2:${AppStorage.Get<number>('count2').toString()} `)
.fontSize(22);
// back()和pushUrl()效果是不同的
Button('返回')
.onClick(() => {
router.back();
})
Button('跳转')
.onClick(() => {
router.pushUrl({ url: 'pages/Index'});
})
}
.width('100%')
.padding(20)
.backgroundColor(0xFFcccc);
}
}
AppStorage的作用相当于HarmonyOS内部集成了一个微型的Redis。
因为AppStorage采用的是键值对
的形式,也提供了对这些键值对
的访问接口,用的最多的就是Get()
和Set()
。
内置组件
下面是一些常用的系统组件简介。
组件名称 | 用途说明 |
---|---|
Text | 文本组件,类似于Android中Label |
TextInput TextArea | 单行或多行文本输入框 |
TextClock TextTimer | 一个显示当前时间,一个显示计数器 |
TextPicker | 文本滑动选择器,通过上下滑动选择需要的文本项 |
Image | 图片组件 |
Button | 按钮,通常用于点击事件,结合Image可以实现多种个性化按钮样式 |
Checkbox | 多选框 |
CheckboxGroup | 用于控制多选框全选或反选 |
Radio | 单选框 |
Progress | 进度条,用于显示加载、下载、处理等进度 |
Slider | 滑杆,通过滑动条来控制设置值,如声音、亮度等 |
Rating | 评价组件,例如常用的五星评价 |
DataPanel | 数据面板,用于数据统计 |
Navigation | 导航组件,一般用于页面的Tabs 容器 |
DataPicker TimePicker | 日期时间选择器 |
Select | 下拉菜单 |
Badge | 新事件标记,显示事件通知 |
Counter | 计数器 |
Stepper StepperItem | 步骤导航器 |
Blank | 空白填充组件 |
Divider | 分隔器组件 |
组件生命周期
页面在跳转时,会被创建、展示和销毁,这实际上也就是组件生命周期的过程变化。
对于自定义组件来说,它的生命周期回调函数
是在特定条件下自动调用的,无法在程序中手动调用。但可以通过重写生命周期函数,实现自定义的操作。
可用的生命周期函数如下。
函数名 | 说明 |
---|---|
aboutToAppear(): void | 组件创建实例后触发调用,该函数在build() 被触发前执行。新创建的组件实例一般会在进入页面栈时调用。在该函数中允许改变状态变量,在后续执行build() 时会采用更新后的组件状态变量 |
onPageShow(): void | 页面显示时触发调用,该函数仅在装饰器@Entry 修饰的组件中生效。当进入到某个页面并显示时会触发该函数,页面由后台进入前台时也会触发该函数 |
onPageHide(): void | 页面隐藏时触发调用,该函数仅在装饰器@Entry 修饰的组件中生效,该函数和onPageShow() 成对出现。当离开某个页面时或页面被其它页面覆盖而隐藏时,会触发该函数 |
onBackPress(): void | 点击设备上的返回按钮时触发,该函数仅在装饰器@Entry 修饰的组件中生效 |
aboutToDisappear(): void | 自定义组件释放之前触发调用,如页面退栈等。不允许在该函数中修改组件状态变量,特别是@Link 装饰的变量 |
import router from '@ohos.router';
@Entry
@Component
struct Index {
@State value: number = 10
// 当进入页面栈时调用
aboutToAppear(): void {
console.log("aboutTOAppear被调用 value=" + this.value)
}
// 当显示出来时调用
onPageShow(): void {
console.log("onPageShow被调用 value=" + this.value)
}
// 当隐藏时调用,如被覆盖
onPageHide(): void {
console.log("onPageHide被调用 value=" + this.value)
}
// 当按下设备上的返回键时调用
onBackPress(): void {
console.log("onBackPress被调用 value=" + this.value)
}
// 当退出页面栈时调用
aboutToDisappear(): void {
console.log("aboutToDisappear被调用 value=" + this.value)
}
build() {
Row() {
Column() {
Text(`value = ${this.value} `).fontSize(50)
Counter() {
Text(this.value.toString()).fontSize(25)
}
.margin(5)
.height(50)
.width(160)
.onInc(() => {
this.value++
})
.onDec(() => {
this.value--
})
Button('push').fontSize(30)
.onClick(() => {
router.pushUrl({
url: "pages/Index"
})
}).margin({ top: 10 })
Button('replace').fontSize(30)
.onClick(() => {
router.replaceUrl({
url: "pages/Index"
})
}).margin({ top: 10 })
Button('back').fontSize(30)
.onClick(() => {
router.back()
}).margin({ top: 10 })
}.width('100%')
}.height('100%')
}
}
下面是程序执行后被触发的生命周期回调函数
输出的日志记录。

感谢支持
更多内容,请移步《超级个体》。