一、创建自定义组件

在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。在进行 UI 界面开发时,通常不是简单的将系统组件进行组合使用,而是需要考虑代码可复用性、业务逻辑与UI分离,后续版本演进等因素。因此,将UI和部分业务逻辑封装成自定义组件是不可或缺的能力。

自定义组件具有以下特点:

● 可组合:允许开发者组合使用系统组件、及其属性和方法。

● 可重用:自定义组件可以被其他组件重用,并作为不同的实例在不同的父组件或容器中使用。

● 数据驱动UI更新:通过状态变量的改变,来驱动UI的刷新。

以下示例展示了自定义组件的基本用法。

@Component
struct HelloComponent {
@State message: string = 'Hello, World!'; build() {
// HelloComponent自定义组件组合系统组件Row和Text
Row() {
Text(this.message)
.onClick(() => {
// 状态变量message的改变驱动UI刷新,UI从'Hello, World!'刷新为'Hello, ArkUI!'
this.message = 'Hello, ArkUI!';
})
}
}
}

  

HelloComponent可以在其他自定义组件中的build()函数中多次创建,实现自定义组件的重用。

class HelloComponentParam {
message: string = ""
} @Entry
@Component
struct ParentComponent {
param: HelloComponentParam = {
message: 'Hello, World!'
} build() {
Column() {
Text('ArkUI message')
HelloComponent(param);
Divider()
HelloComponent(param);
}
}
}

  

要完全理解上面的示例,需要了解自定义组件的以下概念定义,本文将在后面的小节中介绍:

● 自定义组件的基本结构

● 成员函数/变量

● 自定义组件的参数规定

● build()函数

● 自定义组件通用样式

自定义组件的基本结构

● struct:自定义组件基于struct实现,struct + 自定义组件名 + {...}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。

说明:自定义组件名、类名、函数名不能和系统组件名相同。

● @Component:@Component装饰器仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的能力,需要实现build方法描述UI,一个struct只能被一个@Component装饰。

说明:从API version 9开始,该装饰器支持在ArkTS卡片中使用。


@Component
struct MyComponent {
}

  

● build()函数:build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。

@Component
struct MyComponent {
build() {
}
}

  

● @Entry:@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件。@Entry可以接受一个可选的LocalStorage的参数。

说明:

从API version 9开始,该装饰器支持在ArkTS卡片中使用。

从API version 10开始,@Entry可以接受一个可选的LocalStorage的参数或者一个可选的EntryOptions参数。

@Entry
@Component
struct MyComponent {
}

  

EntryOptions10+命名路由跳转选项。

名称

类型

必填

说明

routeName

string

表示作为命名路由页面的名字。

storage

LocalStorage

页面级的UI状态存储。

@Entry({ routeName : 'myPage' })
@Component
struct MyComponent {
}

  

● @Reusable:@Reusable装饰的自定义组件具备可复用能力

说明:

从API version 10开始,该装饰器支持在ArkTS卡片中使用。

@Reusable
@Component
struct MyComponent {
}

  

成员函数/变量

自定义组件除了必须要实现build()函数外,还可以实现其他成员函数,成员函数具有以下约束:

● 不支持静态函数。

● 成员函数的访问始终是私有的。

自定义组件可以包含成员变量,成员变量具有以下约束:

● 不支持静态成员变量。

● 所有成员变量都是私有的,变量的访问规则与成员函数的访问规则相同。

● 自定义组件的成员变量本地初始化有些是可选的,有些是必选的。具体是否需要本地初始化,是否需要从父组件通过参数传递初始化子组件的成员变量,请参考状态管理

自定义组件的参数规定

从上文的示例中,我们已经了解到,可以在build方法或者@Builder装饰的函数里创建自定义组件,在创建的过程中,参数可以被提供给组件。

@Component
struct MyComponent {
private countDownFrom: number = 0;
private color: Color = Color.Blue; build() {
}
} @Entry
@Component
struct ParentComponent {
private someColor: Color = Color.Pink; build() {
Column() {
// 创建MyComponent实例,并将创建MyComponent成员变量countDownFrom初始化为10,将成员变量color初始化为this.someColor
MyComponent({ countDownFrom: 10, color: this.someColor })
}
}
}

  

build()函数

所有声明在build()函数的语言,我们统称为UI描述语言,UI描述语言需要遵循以下规则:

● @Entry装饰的自定义组件,其build()函数下的根节点唯一且必要,且必须为容器组件,其中ForEach禁止作为根节点。 @Component装饰的自定义组件,其build()函数下的根节点唯一且必要,可以为非容器组件,其中ForEach禁止作为根节点。

@Entry
@Component
struct MyComponent {
build() {
// 根节点唯一且必要,必须为容器组件
Row() {
ChildComponent()
}
}
} @Component
struct ChildComponent {
build() {
// 根节点唯一且必要,可为非容器组件
Image('test.jpg')
}
}

  

● 不允许声明本地变量,反例如下。

build() {
// 反例:不允许声明本地变量
let a: number = 1;
}

  

● 不允许在UI描述里直接使用console.info,但允许在方法或者函数里使用,反例如下。

build() {
// 反例:不允许console.info
console.info('print debug log');
}

  

● 不允许创建本地的作用域,反例如下。

build() {
// 反例:不允许本地作用域
{
...
}
}

  

● 不允许调用除了被@Builder装饰以外的方法,允许系统组件的参数是TS方法的返回值。

@Component
struct ParentComponent {
doSomeCalculations() {
} calcTextValue(): string {
return 'Hello World';
} @Builder doSomeRender() {
Text(`Hello World`)
} build() {
Column() {
// 反例:不能调用没有用@Builder装饰的方法
this.doSomeCalculations();
// 正例:可以调用
this.doSomeRender();
// 正例:参数可以为调用TS方法的返回值
Text(this.calcTextValue())
}
}
}

  

● 不允许使用switch语法,如果需要使用条件判断,请使用if。反例如下。

build() {
Column() {
// 反例:不允许使用switch语法
switch (expression) {
case 1:
Text('...')
break;
case 2:
Image('...')
break;
default:
Text('...')
break;
}
}
}

  

● 不允许使用表达式,反例如下。

build() {
Column() {
// 反例:不允许使用表达式
(this.aVar > 10) ? Text('...') : Image('...')
}
}

  

自定义组件通用样式

自定义组件通过“.”链式调用的形式设置通用样式。

@Component
struct MyComponent2 {
build() {
Button(`Hello World`)
}
} @Entry
@Component
struct MyComponent {
build() {
Row() {
MyComponent2()
.width(200)
.height(300)
.backgroundColor(Color.Red)
}
}
}

  

说明:

ArkUI给自定义组件设置样式时,相当于给MyComponent2套了一个不可见的容器组件,而这些样式是设置在容器组件上的,而非直接设置给MyComponent2的Button组件。通过渲染结果我们可以很清楚的看到,背景颜色红色并没有直接生效在Button上,而是生效在Button所处的开发者不可见的容器组件上。

二、页面和自定义组件生命周期

在开始之前,我们先明确自定义组件和页面的关系:

● 自定义组件:@Component装饰的UI单元,可以组合多个系统组件实现UI的复用。

● 页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。

页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:

● onPageShow:页面每次显示时触发。

● onPageHide:页面每次隐藏时触发一次。

● onBackPress:当用户点击返回按钮时触发。

组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:

● aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。

● aboutToDisappear:在自定义组件即将析构销毁时执行。

生命周期流程如下图所示,下图展示的是被@Entry装饰的组件(首页)生命周期。

根据上面的流程图,我们从自定义组件的初始创建、重新渲染和删除来详细解释。

自定义组件的创建和渲染流程

1.  自定义组件的创建:自定义组件的实例由ArkUI框架创建。

2.  初始化自定义组件的成员变量:通过本地默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序。

3.  如果开发者定义了aboutToAppear,则执行aboutToAppear方法。

4.  在首次渲染的时候,执行build方法渲染系统组件,如果有自定义子组件,则创建自定义组件的实例。在执行build()函数的过程中,框架会观察每个状态变量的读取状态,将保存两个map:

a.  状态变量 -> UI组件(包括ForEach和if)。

b.  UI组件 -> 此组件的更新函数,即一个lambda方法,作为build()函数的子集,创建对应的UI组件并执行其属性方法,示意如下。

build() {
...
this.observeComponentCreation(() => {
Button.create();
}) this.observeComponentCreation(() => {
Text.create();
})
...
}

  

当应用在后台启动时,此时应用进程并没有销毁,所以仅需要执行onPageShow。

自定义组件重新渲染

当事件句柄被触发(比如设置了点击事件,即触发点击事件)改变了状态变量时,或者LocalStorage / AppStorage中的属性更改,并导致绑定的状态变量更改其值时:

1.  框架观察到了变化,将启动重新渲染。

2.  根据框架持有的两个map(自定义组件的创建和渲染流程中第4步),框架可以知道该状态变量管理了哪些UI组件,以及这些UI组件对应的更新函数。执行这些UI组件的更新函数,实现最小化更新。

自定义组件的删除

如果if组件的分支改变,或者ForEach循环渲染中数组的个数改变,组件将被删除:

1.  在删除组件之前,将调用其aboutToDisappear生命周期函数,标记着该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,当前端节点已经没有引用时,将被JS虚拟机垃圾回收。

2.  自定义组件和它的变量将被删除,如果其有同步的变量,比如@Link@Prop@StorageLink,将从同步源上取消注册。

不建议在生命周期aboutToDisappear内使用async await,如果在生命周期的aboutToDisappear使用异步操作(Promise或者回调方法),自定义组件将被保留在Promise的闭包中,直到回调方法被执行完,这个行为阻止了自定义组件的垃圾回收。

以下示例展示了生命周期的调用时机:

// Index.ets
import router from '@ohos.router'; @Entry
@Component
struct MyComponent {
@State showChild: boolean = true; // 只有被@Entry装饰的组件才可以调用页面的生命周期
onPageShow() {
console.info('Index onPageShow');
}
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onPageHide() {
console.info('Index onPageHide');
} // 只有被@Entry装饰的组件才可以调用页面的生命周期
onBackPress() {
console.info('Index onBackPress');
} // 组件生命周期
aboutToAppear() {
console.info('MyComponent aboutToAppear');
} // 组件生命周期
aboutToDisappear() {
console.info('MyComponent aboutToDisappear');
} build() {
Column() {
// this.showChild为true,创建Child子组件,执行Child aboutToAppear
if (this.showChild) {
Child()
}
// this.showChild为false,删除Child子组件,执行Child aboutToDisappear
Button('delete Child').onClick(() => {
this.showChild = false;
})
// push到Page2页面,执行onPageHide
Button('push to next page')
.onClick(() => {
router.pushUrl({ url: 'pages/Page2' });
})
} }
} @Component
struct Child {
@State title: string = 'Hello World';
// 组件生命周期
aboutToDisappear() {
console.info('[lifeCycle] Child aboutToDisappear')
}
// 组件生命周期
aboutToAppear() {
console.info('[lifeCycle] Child aboutToAppear')
} build() {
Text(this.title).fontSize(50).onClick(() => {
this.title = 'Hello ArkUI';
})
}
}

  

以上示例中,Index页面包含两个自定义组件,一个是被@Entry装饰的MyComponent,也是页面的入口组件,即页面的根节点;一个是Child,是MyComponent的子组件。只有@Entry装饰的节点才可以生效页面的生命周期方法,所以MyComponent中声明了当前Index页面的页面生命周期函数。MyComponent和其子组件Child也同时声明了组件的生命周期函数。

● 应用冷启动的初始化流程为:MyComponent aboutToAppear --> MyComponent build --> Child aboutToAppear --> Child build --> Child build执行完毕 --> MyComponent build执行完毕 --> Index onPageShow。

● 点击“delete Child”,if绑定的this.showChild变成false,删除Child组件,会执行Child aboutToDisappear方法。

● 点击“push to next page”,调用router.pushUrl接口,跳转到另外一个页面,当前Index页面隐藏,执行页面生命周期Index onPageHide。此处调用的是router.pushUrl接口,Index页面被隐藏,并没有销毁,所以只调用onPageHide。跳转到新页面后,执行初始化新页面的生命周期的流程。

● 如果调用的是router.replaceUrl,则当前Index页面被销毁,执行的生命周期流程将变为:Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。上文已经提到,组件的销毁是从组件树上直接摘下子树,所以先调用父组件的aboutToDisappear,再调用子组件的aboutToDisappear,然后执行初始化新页面的生命周期流程。

● 点击返回按钮,触发页面生命周期Index onBackPress,且触发返回一个页面后会导致当前Index页面被销毁。

● 最小化应用或者应用进入后台,触发Index onPageHide。当前Index页面没有被销毁,所以并不会执行组件的aboutToDisappear。应用回到前台,执行Index onPageShow。

● 退出应用,执行Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。

OpenHarmony自定义组件介绍的更多相关文章

  1. Android开发之自定义组件和接口回调

    说到自定义控件不得不提的就是接口回调,在Android开发中接口回调用的还是蛮多的.在这篇博客开始的时候呢,我想聊一下iOS的自定义控件.在iOS中自定义控件的思路是继承自UIView, 在UIVie ...

  2. .NET平台开源项目速览(6)FluentValidation验证组件介绍与入门(一)

    在文章:这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧!(第二辑)中,给大家初步介绍了一下FluentValidation验证组件.那里只是概述了一下,并没有对其使用和强大功能做深入研究 ...

  3. Android开发——构建自定义组件

    Android中,你的应用程序程序与View类组件有着一种固定的联系,例如按钮(Button). 文本框(TextView), 可编辑文本框(EditText), 列表框(ListView), 复选框 ...

  4. Log4j自定义Appender介绍

    转自:http://gemantic.iteye.com/blog/1234996 最初想要在执行一段业务逻辑的时候调用一个外部接口记录审计信息,一直找不到一个比较优雅的方式,经过讨论觉得log4j自 ...

  5. Android官方架构组件介绍之LifeCycle

    Google 2017 I/O开发者大会于近日召开,在开发者大会上谷歌除了发布了Android O等一些新产品之外,也对Android代码的架构做出了一个官方的回应. Google 2017 I/O开 ...

  6. 【Android 应用开发】 自定义组件 宽高适配方法, 手势监听器操作组件, 回调接口维护策略, 绘制方法分析 -- 基于 WheelView 组件分析自定义组件

    博客地址 : http://blog.csdn.net/shulianghan/article/details/41520569 代码下载 : -- GitHub : https://github.c ...

  7. JMeter主要组件介绍

    JMeter主要组件介绍   转自https://www.cnblogs.com/linbo3168/p/6023962.html 作者:linbo.yang 1.测试计划(Test Plan)是使用 ...

  8. 第三部分:Android 应用程序接口指南---第二节:UI---第十二章 自定义组件

    第12章 自定义组件 Android平台提供了一套完备的.功能强大的组件化模型用于搭建用户界面,这套组件化模型以View和 ViewGroup这两个基础布局类为基础.平台本身已预先实现了多种用于构建界 ...

  9. 微信小程序 发现之旅(二)—— 自定义组件

    组件化的项目开发中,组件应当划分为三个层次:组件.模块.页面 微信小程序已经为开发者封装好了基础组件,页面文件(pages)也有了详细的规定 而模块就需要自行开发,并且要和页面文件区分开,这就涉及到自 ...

  10. NET平台开源项目速览(6)FluentValidation验证组件介绍与入门(转载)

    原文地址:http://www.cnblogs.com/asxinyu/p/dotnet_Opensource_project_FluentValidation_1.html 阅读目录 1.基本介绍 ...

随机推荐

  1. 亲测可行,Android Studio 查看源码出现 Source for ‘Android API xxx Platform’ not found 的解决方法

    亲测可行,Android Studio 查看源码出现 Source for 'Android API xxx Platform' not found 的解决方法 如标题中的问题,产生的原因就是 SDK ...

  2. Golang条件编译介绍

    相信熟悉 Golang 的小伙伴不少都知道 条件编译 这个事,最近项目中也可能会用到这个东西.所以特意重新学习下,记录下学习的过程.这样用的时候记不住了,还可以直接过来看自己的笔记. 文章很多内容来源 ...

  3. 第124篇: 期约Promise基本方法

    好家伙,本篇为<JS高级程序设计>第十章"期约与异步函数"学习笔记   1.异步编程 同步行为和异步行为的对立统一是计算机科学的一个基本概念. 特别是在 JavaScr ...

  4. 【Azure 应用服务】在Azure Funciton中使用Powershell脚本函数,需要存储一些变量值如何解决?

    问题描述 使用Azure Function创建Powershell脚本来执行函数,在使用中需要存储一些不重要的参数.一般情况,存储的问题都是交给DB,Storage等来解决.但是有没有一种简单的办法呢 ...

  5. 【Azure 应用服务】Azure Function HTTP Trigger 遇见奇妙的500 Internal Server Error: Failed to forward request to http://169.254.130.x

    问题描述 使用 Azure Funciton App,在本地运行完全成功的Python代码,发布到Azure Function就出现了500  Internal Server Error. 而且错误消 ...

  6. 【Azure Redis 缓存】Redis的监控方式? 是否有API接口调用来获取监控值

    问题描述 对于PaaS的Azure Cache for Redis,Azure中有哪些监控方式?是否能有api接口调用来获取监控值? 问题答案 1) 在Redis的门户中,使用Metrics查看Red ...

  7. 6、mysql的SQL优化

    1. 大批量插入数据 1) 主键顺序插入 因为InnoDB类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率.如果InnoDB表没有主键,那么系统会自动默 ...

  8. CF1362C Johnny and Another Rating Drop(二进制、复杂度考虑)

    看完数据范围\(n\in[1,1e18]\)就可以先猜一下要不是可以直接推公式,不能暴力去做,更不能遍历一遍,又看到这种2进制的题目,要猜是不是\(log\)级别的复杂度. 可以依次考虑每一位 \(所 ...

  9. 当未指定且存在多个构造器,实例化对象时Spring如何选择?

    前言 在前面的讲解中,我们了解了如何获取构造器.当只有一个符合条件的构造器时,自然会选择它作为初始化的构造器.然而,在上一节中,我们遇到了一种特殊情况:当有多个符合条件的构造器时,返回的是一个数组.在 ...

  10. redis---面经

    redis 偏应用的总结:redis 应用 Redis是什么? Redis是什么 对象 字符串 自增,键值对. SDS数据结构记录长度,已经使用,和总共长度,并且提前多余出容量,防止一直扩容缩容. 字 ...