目录
一、引言
二、ArkUI 自定义组件初相识
三、自定义组件的基础结构剖析
(一)struct 关键字
(二)@Component 装饰器
(三)build () 函数
(四)@Entry 装饰器
四、成员函数与变量规则
(一)成员函数
(二)成员变量
五、参数规定与传递
六、实战演练:构建自定义组件
(一)创建组件目录
(二)编写组件代码
(三)在页面中引入和使用组件
七、总结与展望
一、引言

在当今数字化时代,用户界面(UI)的设计与开发对于应用程序的成功至关重要。一个美观、易用且高效的 UI 能够吸引用户,并提升他们的使用体验。ArkUI 作为一种先进的 UI 开发框架,正逐渐在界面开发领域崭露头角,发挥着重要作用。
ArkUI 为开发者提供了简洁而强大的工具和语法,使得创建复杂且富有交互性的界面变得更加容易。它不仅具备丰富的内置组件和灵活的布局系统,还支持高效的动画和交互效果实现,能够满足各种应用场景的需求。无论是开发移动端应用、桌面应用还是网页应用,ArkUI 都能为开发者提供有力的支持。
在实际的界面开发过程中,我们常常会遇到一些挑战。例如,随着项目规模的增大,代码的可维护性和复用性变得愈发重要。如果只是简单地使用 ArkUI 提供的基础组件进行堆砌,可能会导致代码冗长、难以管理,并且在需要修改或扩展功能时,会耗费大量的时间和精力。这时,自定义组件就成为了解决这些问题的关键。
自定义组件允许开发者将一组相关的 UI 元素和功能封装在一起,形成一个独立的、可复用的模块。通过自定义组件,我们可以将复杂的界面逻辑抽象出来,使得代码结构更加清晰,易于维护和扩展。同时,自定义组件还能够提高开发效率,减少重复代码的编写。当我们在多个页面或项目中需要使用相同的 UI 元素或功能时,只需创建一次自定义组件,然后在需要的地方进行复用即可。
自定义组件对于提升开发效率和代码质量具有不可忽视的重要性。在接下来的内容中,我们将深入探讨 ArkUI 自定义组件的基本结构,帮助大家更好地掌握这一强大的功能。
二、ArkUI 自定义组件初相识
在 ArkUI 的世界里,组件是构成用户界面的基本单元,就如同搭建积木时的一块块积木。其中,由框架直接提供的组件,像常见的文本组件(Text)、按钮组件(Button)、容器组件(如 Row、Column)等,被称为系统组件 。这些系统组件是构建界面的基础元素,开发者可以直接使用它们来快速搭建出简单的界面结构。例如,使用 Text 组件来显示文字信息,用 Button 组件来创建可点击的按钮,通过 Row 和 Column 组件来组织和排列其他组件的位置。
而自定义组件,则是开发者根据具体业务需求,自行创建的具有特定功能和样式的组件。它就像是开发者根据自己的创意,将已有的积木进行重新组合和加工,创造出的独特积木块。与系统组件相比,自定义组件具有更强的针对性和灵活性。系统组件虽然功能通用,但在面对复杂多变的业务场景时,可能无法完全满足需求。比如,在一个电商应用中,需要一个具有特定样式和交互逻辑的商品展示卡片,系统组件无法直接提供这样的功能,这时就需要开发者创建自定义组件来实现。
自定义组件在实际开发中具有诸多显著优势,首先是提高代码复用性。假设在一个大型应用中,有多个页面都需要用到相同的 UI 元素,如一个特定样式的导航栏。如果每次都重复编写相同的代码来实现这个导航栏,不仅会导致代码量大幅增加,而且后期维护和修改也会变得异常困难。而通过将导航栏封装成一个自定义组件,只需要编写一次代码,然后在各个需要的页面中进行复用即可。这样不仅减少了代码的重复编写,还提高了代码的可维护性,当需要修改导航栏的样式或功能时,只需要在自定义组件中进行修改,所有使用该组件的页面都会自动更新。
其次,自定义组件有助于增强业务逻辑与 UI 的分离。在开发过程中,将业务逻辑和 UI 展示分离是一种良好的编程实践。自定义组件可以将相关的 UI 元素和业务逻辑封装在一起,使得代码结构更加清晰。例如,在一个用户登录功能中,可以将登录表单、验证逻辑以及提交按钮等相关元素封装成一个自定义的登录组件。这样,在主页面中只需要引入这个自定义组件,而不需要关心其内部的具体实现细节,从而降低了主页面的复杂度,提高了代码的可读性和可维护性。
另外,自定义组件还为后续版本演进提供了便利。随着业务的发展和需求的变化,应用程序需要不断进行更新和升级。如果使用自定义组件,在进行版本演进时,可以更加方便地对组件进行修改和扩展,而不会影响到其他部分的代码。比如,当需要为电商应用的商品展示卡片添加新的功能时,只需要在商品展示自定义组件中进行修改,而不会对应用的其他页面和功能造成影响。
总之,自定义组件在 ArkUI 开发中扮演着至关重要的角色,它为开发者提供了更高的灵活性和可扩展性,能够帮助开发者更加高效地构建出高质量的用户界面。
三、自定义组件的基础结构剖析
了解了自定义组件的概念和优势后,接下来深入剖析 ArkUI 自定义组件的基本结构。一个完整的自定义组件通常由以下几个关键部分组成:struct 关键字、@Component 装饰器、build () 函数以及 @Entry 装饰器 。这些部分相互协作,共同构成了自定义组件的基础框架,下面我们将逐一进行详细讲解。
(一)struct 关键字
在 ArkUI 中,自定义组件是基于 struct(结构体)来实现的。struct 关键字用于定义一个新的数据类型,它可以包含多个成员变量和成员函数。在自定义组件的场景下,struct + 自定义组件名 + {…} 的组合构成了自定义组件的基本结构。例如,我们要创建一个简单的自定义按钮组件,可以这样定义:
struct CustomButton {
// 这里可以定义组件的成员变量,比如按钮的文本、颜色等
private buttonText: string;
private buttonColor: Color;
// 这里可以定义组件的成员函数,比如按钮的点击处理函数
clickHandler() {
// 处理按钮点击后的逻辑
}
}
需要注意的是,自定义组件的 struct 不能有继承关系,这是为了保证组件结构的简单性和独立性,避免因继承带来的复杂性和潜在问题。同时,对于 struct 的实例化,可以省略 new 关键字,这使得代码更加简洁。例如,在使用上述 CustomButton 组件时,可以直接这样写:
@Entry
@Component
struct ParentComponent {
build() {
Column() {
CustomButton({ buttonText: '点击我', buttonColor: Color.Blue })
}
}
}
(二)@Component 装饰器
@Component 装饰器是赋予 struct 组件化能力的关键。它仅能修饰 struct 关键字声明的数据结构,当一个 struct 被 @Component 装饰后,就具备了组件化的能力,能够在界面中作为一个独立的组件使用。被修饰的 struct 需要实现 build 方法,通过链式调用的方式来描述组件的 UI 结构。例如:
@Component
struct CustomButton {
@State private buttonText: string = '默认文本';
@State private buttonColor: Color = Color.Gray;
build() {
Button(this.buttonText)
.backgroundColor(this.buttonColor)
.onClick(() => {
// 按钮点击后的逻辑
})
}
}
这里的 CustomButton 组件通过 @Component 装饰器,将其内部的 UI 结构(一个具有特定文本和颜色,且能响应点击事件的按钮)封装起来,使其可以在其他组件中复用。同时,一个 struct 只能被一个 @Component 装饰,这是为了保证组件定义的唯一性和规范性。从 API version 9 开始,该装饰器支持在 ArkTS 卡片中使用,进一步拓展了其应用场景。
(三)build () 函数
build () 函数在自定义组件中扮演着至关重要的角色,它用于定义自定义组件的声明式 UI 描述。简单来说,组件最终在界面上呈现的样子,就是由 build () 函数中的代码来决定的。自定义组件必须定义 build () 函数,否则无法正常渲染。
在 @Entry 装饰的自定义组件中,其 build () 函数下的根节点唯一且必要,并且必须为容器组件,例如 Row、Column、Stack 等。这是因为根容器组件能够容纳其他子组件,为整个组件的布局提供基础结构。同时,ForEach 禁止作为根节点,这是为了避免一些潜在的布局和数据绑定问题。例如:
@Entry
@Component
struct MainPage {
build() {
Column() {
// 在这里添加其他子组件
}
}
}
而在 @Component 装饰的自定义组件中,其 build () 函数下的根节点同样唯一且必要,但可以为非容器组件,比如 Text、Image 等。这使得自定义组件的构建更加灵活,可以根据具体需求选择合适的根节点类型。同样,ForEach 也禁止作为根节点。
此外,build () 函数还有一些其他的约束规则。在 build () 函数中,不允许声明本地变量或者本地代码块(作用域),这是为了保证 UI 描述的简洁性和清晰性,避免在 UI 构建过程中引入不必要的复杂性。例如,以下代码是不允许的:
build() {
// 反例:不允许声明本地变量
let a: number = 1;
// 反例:不允许声明本地代码块
{
// 这里的代码块会报错
}
}
同时,不允许在 UI 描述里直接使用console.info,但允许在函数里使用。这是因为 UI 描述主要关注组件的外观和布局,而console.info通常用于调试输出,将其放在函数中可以更好地分离调试逻辑和 UI 构建逻辑。另外,不允许调用没有用 @Builder 装饰的方法,这是为了确保 UI 构建过程的可控性和一致性。不过,允许系统组件的参数是 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());
}
}
}
另外,build () 函数中不允许使用 switch 语法,如果需要使用条件判断,请使用 if;也不允许使用三元表达式。这是为了统一代码风格,提高代码的可读性和可维护性。同时,不允许直接改变状态变量,因为状态变量的改变应该通过合理的事件处理和状态管理机制来实现,以确保 UI 的正确更新和数据的一致性。例如:
@Component
struct CompA {
@State col1: Color = Color.Yellow;
@State col2: Color = Color.Green;
@State count: number = 1;
build() {
Column() {
// 应避免直接在Text组件内改变count的值
// Text(`${this.count++}`).width(50).height(50).fontColor(this.col1).onClick(() => {
// this.col2 = Color.Red;
// });
}
}
}
(四)@Entry 装饰器
@Entry 装饰器的作用是标记一个自定义组件作为 UI 页面的入口。在单个 UI 页面中,最多只能使用 @Entry 装饰一个自定义组件,这是为了明确页面的入口点,保证页面结构的清晰和有序。例如:
@Entry
@Component
struct MainPage {
build() {
// 页面的UI描述
}
}
从 API version 9 开始,@Entry 可以接受一个可选的 LocalStorage 参数,这为页面与本地存储数据的交互提供了便利。通过这个参数,页面可以在加载时获取本地存储中的数据,并在数据发生变化时进行相应的更新。例如:
class PropB {
code: number;
constructor(code: number) {
this.code = code;
}
}
// 创建新实例并使用给定对象初始化
let para: Record<string, number> = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para);
storage.setOrCreate('PropB', new PropB(50));
@Entry(storage)
@Component
struct CompA {
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@LocalStorageLink('PropA') parentLinkNumber: number = 1;
// @LocalStorageLink变量装饰器与LocalStorage中的'PropB'属性建立双向绑定
@LocalStorageLink('PropB') parentLinkObject: PropB = new PropB(0);
build() {
Column({ space: 15 }) {
Button(`Parent from LocalStorage ${this.parentLinkNumber}`)
// initial value from LocalStorage will be 47, because 'PropA' initialized already.
.onClick(() => {
this.parentLinkNumber += 1;
})
Button(`Parent from LocalStorage ${this.parentLinkObject.code}`)
// initial value from LocalStorage will be 50, because 'PropB' initialized already.
.onClick(() => {
this.parentLinkObject.code += 1;
})
// @Component子组件自动获得对CompA LocalStorage实例的访问权限。
Child()
}
}
}
@Component
struct Child {
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@LocalStorageLink('PropA') childLinkNumber: number = 1;
// @LocalStorageLink变量装饰器与LocalStorage中的'PropB'属性建立双向绑定
@LocalStorageLink('PropB') childLinkObject: PropB = new PropB(0);
build() {
Column() {
Button(`Child from LocalStorage ${this.childLinkNumber}`)
// 更改将同步至LocalStorage中的'PropA'以及Parent.parentLinkNumber
.onClick(() => {
this.childLinkNumber += 1;
})
Button(`Child from LocalStorage ${this.childLinkObject.code}`)
// 更改将同步至LocalStorage中的'PropB'以及Parent.parentLinkObject.code
.onClick(() => {
this.childLinkObject.code += 1;
})
}
}
}
在这个例子中,CompA 组件通过 @Entry (storage) 接受了 LocalStorage 参数,使得组件内部可以通过 @LocalStorageLink 装饰器与 LocalStorage 中的属性建立双向绑定,实现数据的共享和同步更新。
四、成员函数与变量规则
(一)成员函数
在自定义组件中,成员函数具有一些特定的约束。首先,不支持静态函数,这是因为静态函数通常与类本身相关联,而不是与类的实例相关联。在自定义组件的场景下,更强调组件实例的独立性和个性化,静态函数无法满足这一需求。例如:
@Component
struct CustomComponent {
// 反例:不允许定义静态函数
static someStaticFunction() {
// 这里的代码会报错
}
}
其次,成员函数的访问始终是私有的。这意味着在组件外部,无法直接调用组件的成员函数。这种私有访问的设计,有助于保护组件内部的实现细节,防止外部代码随意修改组件的内部状态,从而提高代码的安全性和稳定性。例如,在一个自定义的用户信息展示组件中,可能有一个内部的计算函数,用于处理用户信息的格式化和显示逻辑,这个函数就不应该被外部直接调用,而只在组件内部使用:
@Component
struct UserInfoComponent {
private user: { name: string; age: number };
private formatUserInfo(): string {
return `${this.user.name},${this.user.age}岁`;
}
build() {
Text(this.formatUserInfo());
}
}
在这个例子中,formatUserInfo函数是私有的,只能在UserInfoComponent组件内部被调用,外部组件无法直接访问它。
(二)成员变量
自定义组件中的成员变量同样遵循一定的规则。首先,不支持静态成员变量,原因与不支持静态函数类似,静态成员变量不符合组件实例化和个性化的需求。例如:
@Component
struct CustomComponent {
// 反例:不允许定义静态成员变量
static someStaticVariable: number = 1;
}
其次,所有成员变量都是私有的,变量的访问规则与成员函数的访问规则相同。这保证了组件内部数据的安全性,防止外部代码对组件内部变量的随意修改,维护了组件的独立性和完整性。例如,在一个图片展示组件中,可能有一个私有成员变量用于存储图片的路径:
@Component
struct ImageComponent {
private imagePath: string;
constructor(imagePath: string) {
this.imagePath = imagePath;
}
build() {
Image(this.imagePath);
}
}
在这个例子中,imagePath变量是私有的,外部组件无法直接访问和修改它,只能通过ImageComponent组件提供的接口来使用这个变量。
此外,自定义组件的成员变量本地初始化有些是可选的,有些是必选的。具体是否需要本地初始化,是否需要从父组件通过参数传递初始化子组件的成员变量,请参考状态管理。例如,在一个计数器组件中,count变量可以在本地初始化,也可以通过父组件传递参数来初始化:
@Component
struct CounterComponent {
@State private count: number;
constructor(count: number = 0) {
this.count = count;
}
build() {
Column() {
Text(`${this.count}`)
Button('增加')
.onClick(() => {
this.count++;
})
}
}
}
在这个例子中,count变量可以在构造函数中接受一个可选参数进行初始化,如果没有传递参数,则使用默认值 0 进行本地初始化。
五、参数规定与传递
在创建自定义组件时,参数的传递是实现组件灵活性和复用性的关键。我们可以在 build 方法或者 @Builder 装饰的函数里创建自定义组件,并在创建过程中向组件传递参数,这些参数将用于初始化自定义组件的成员变量。
例如,我们有一个自定义的倒计时组件CountDownComponent,它需要接收一个初始倒计时的数值和倒计时的颜色作为参数:
@Component
struct CountDownComponent {
private countDownFrom: number;
private color: Color;
constructor(countDownFrom: number, color: Color) {
this.countDownFrom = countDownFrom;
this.color = color;
}
build() {
Text(`${this.countDownFrom}`)
.fontColor(this.color)
.onClick(() => {
if (this.countDownFrom > 0) {
this.countDownFrom--;
}
});
}
}
在父组件中,我们可以这样创建并传递参数给CountDownComponent:
@Entry
@Component
struct ParentComponent {
build() {
Column() {
CountDownComponent(60, Color.Red)
}
}
}
在这个例子中,ParentComponent通过CountDownComponent(60, Color.Red)创建了CountDownComponent的实例,并将60和Color.Red作为参数传递给它,分别初始化了countDownFrom和color成员变量。
再比如,我们有一个更复杂的自定义表单组件CustomFormComponent,它接收多个参数来配置表单的各个字段:
@Component
struct CustomFormComponent {
private username: string;
private password: string;
private submitText: string;
constructor(username: string, password: string, submitText: string) {
this.username = username;
this.password = password;
this.submitText = submitText;
}
build() {
Column() {
TextField({ placeholder: this.username })
SecureField({ placeholder: this.password })
Button(this.submitText)
.onClick(() => {
// 处理表单提交逻辑
});
}
}
}
在父组件中使用时:
@Entry
@Component
struct MainComponent {
build() {
Column() {
CustomFormComponent('请输入用户名', '请输入密码', '登录')
}
}
}
这里MainComponent向CustomFormComponent传递了三个参数,分别用于设置表单的用户名提示、密码提示和提交按钮的文本。
通过合理地定义和传递参数,我们可以让自定义组件在不同的场景下具有不同的表现,极大地提高了组件的复用性和灵活性,满足各种复杂的业务需求。
六、实战演练:构建自定义组件
为了更直观地理解 ArkUI 自定义组件的开发过程,我们通过一个完整的示例来展示如何构建一个可复用的按钮组件。这个按钮组件不仅具有独特的样式,还能实现特定的交互逻辑。
(一)创建组件目录
首先,在项目的pages同级目录中,创建一个名为components的目录,用于存放所有的自定义组件。如果你的项目结构有特殊要求,也可以根据自己的习惯设置其他名称的目录。
(二)编写组件代码
在components目录中,右键选择 “New> ArkTS File”,输入组件名 “CustomButton”(注意组件名首字母大写)。然后,在CustomButton.ets文件中编写如下代码:
@Component
export struct CustomButton {
// 按钮的文本
@Prop private buttonText: string;
// 按钮的背景颜色
@Prop private buttonBgColor: Color;
// 按钮的文本颜色
@Prop private buttonTextColor: Color;
// 按钮的点击处理函数
@Builder private clickHandler: () => void;
build() {
Button(this.buttonText)
.backgroundColor(this.buttonBgColor)
.fontColor(this.buttonTextColor)
.width('200px')
.height('60px')
.fontSize(20)
.borderRadius(10)
.onClick(this.clickHandler);
}
}
在这段代码中,我们使用@Component装饰器将CustomButton结构体标记为一个组件。通过@Prop装饰器定义了四个属性,分别是按钮的文本buttonText、背景颜色buttonBgColor、文本颜色buttonTextColor以及点击处理函数clickHandler。在build函数中,我们使用Button组件来构建自定义按钮的 UI 结构,并通过链式调用设置了按钮的各种样式属性,如背景颜色、文本颜色、宽度、高度、字体大小、边框圆角等,同时为按钮绑定了点击事件处理函数。
(三)在页面中引入和使用组件
接下来,在需要使用这个自定义按钮组件的页面中,我们需要先引入它。假设我们在pages/Index.ets页面中使用,代码如下:
import { CustomButton } from '../components/CustomButton';
@Entry
@Component
struct Index {
@State private count: number = 0;
increment() {
this.count++;
}
build() {
Column() {
Text(`点击次数: ${this.count}`)
.fontSize(25)
.margin(20);
CustomButton({
buttonText: '点击我',
buttonBgColor: Color.Blue,
buttonTextColor: Color.White,
clickHandler: this.increment.bind(this)
})
.margin(20);
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(AlignItems.Center);
}
}
在上述代码中,首先通过import语句从components目录中引入了CustomButton组件。然后,在Index组件的build函数中,使用CustomButton组件,并通过传递不同的属性值来定制按钮的外观和行为。这里设置了按钮的文本为 “点击我”,背景颜色为蓝色,文本颜色为白色,点击处理函数绑定到Index组件的increment方法,该方法用于增加count变量的值,并在页面上显示点击次数。
通过以上步骤,我们成功创建了一个自定义的按钮组件,并在页面中进行了引入和使用。这个按钮组件可以在不同的页面中复用,只需要传递不同的属性值,就可以满足各种不同的业务需求。
七、总结与展望
通过对 ArkUI 自定义组件基本结构的深入探索,我们对其有了全面而清晰的认识。自定义组件作为 ArkUI 开发中的关键技术,具有至关重要的作用。
从基本结构来看,struct 关键字奠定了组件的基础框架,它就像是组件的 “骨架”,支撑起整个组件的结构;@Component 装饰器赋予组件生命,使其具备在界面中展示和交互的能力,如同赋予 “骨架” 以灵魂;build () 函数则是定义组件外观和行为的核心,决定了组件最终呈现在用户面前的样子,它是组件的 “外在表现”;而 @Entry 装饰器则明确了页面的入口,就像为用户指明了进入组件世界的大门。
成员函数和变量规则,以及参数规定与传递机制,为组件的功能实现和数据交互提供了保障。它们使得组件能够根据不同的需求进行灵活定制,实现多样化的功能。在实际开发中,我们通过实战演练构建了自定义按钮组件,亲身体验了从创建组件目录、编写组件代码到在页面中引入和使用组件的全过程,这不仅加深了我们对理论知识的理解,还提高了我们的实际动手能力。
掌握 ArkUI 自定义组件的知识,对于开发者来说具有不可估量的重要性。它能够极大地提高开发效率,减少重复代码的编写。通过将常用的 UI 元素和功能封装成自定义组件,我们可以在不同的项目或页面中快速复用,节省开发时间和精力。同时,这也有助于提升代码的可维护性,使代码结构更加清晰,便于团队协作开发和后期的维护与升级。
展望未来,随着技术的不断发展和应用场景的日益丰富,自定义组件在更多复杂场景中的应用前景将十分广阔。在人工智能与物联网深度融合的时代,智能家居系统的控制界面、智能健康监测设备的交互界面等,都需要高度定制化的 UI 组件来满足用户的个性化需求。自定义组件能够轻松应对这些复杂场景,通过灵活的组合和定制,为用户提供更加便捷、高效、个性化的交互体验。相信在未来,ArkUI 自定义组件将在更多领域发挥重要作用,为开发者带来更多的惊喜和可能。


















暂无评论内容