HarmonyOS Next应用分层架构下组件封装开发实践

基于鸿蒙应用分层架构的ArkUI组件封装实践

在鸿蒙应用开发中,合理利用 ArkUI 组件进行封装,可以实现代码复用,提升开发效率。本文将结合鸿蒙应用分层架构的特点,详细探讨几个典型的 ArkUI 组件封装场景及其实现方案。

华为鸿蒙应用分层架构概览

鸿蒙应用架构分层:将应用划分为产品定制层、基础特性层和公共能力层,可以降低层间的依赖性,从而提升代码的可维护性。通过分层架构设计进一步明确了每层的职责和层间的交互机制,为开发者呈现了一个清晰且结构化的开发框架,通过封装可复用的组件,为上层业务逻辑提供支持。

一、公用组件封装

在应用开发中,不同业务场景常需要相同功能和样式的 ArkUI 组件,例如统一的登录按钮或弹窗按钮。以 Button 组件为例,封装统一的样式和逻辑后,可提供给不同业务团队使用。

封装方案探讨

传统方法是通过自定义组件,并将通用逻辑抽取封装。但此方法存在一些问题,如使用方式与系统组件不一致、自定义组件入参过大、不利于后期维护。

为解决这些问题,ArkTS 提供了 attributeModifier 属性方法。通过自定义 Class 类实现 AttributeModifier 接口,可对系统组件属性进行扩展。

实现方案对比

方案一:适用于组合多个系统组件的场景。提供方创建自定义组件并导出,同时接受外部传入的 AttributeModifier 类实例。

方案二:适用于单一类型组件的场景。提供方直接创建 AttributeModifier 接口实现类,并导出供使用方调用。

二、分层架构介绍和实现

产品定制层

产品定制层的各个子目录会被编译成一个Entry类型的HAP,作为应用的主入口。该层面向多种设备,集成相应功能和特性。产品定制层划分为多个功能模块,每个模块针对特定设备或使用场景设计,并根据产品需求进行功能和交互的定制开发。在产品定制层,开发者可以从不同设备对应的应用UX设计和功能两个维度,结合具体的业务场景,选择一次编译生成相同或者不同的HAP(或其组合)。通过使用定制多目标构建产物的定制功能,可以将应用所对应的HAP编译成各自的.app文件,用于上架到应用市场。

基础特性层

在基础特性层中,功能模块根据部署需求被分为两类。对于需要通过Ability承载的功能,可以设计为Feature类型的HAP,而对于不需要通过Ability承载的功能,根据是否需要实现按需加载,可以选择设计为HAR模块或者HSP模块,编译后对应HAR包或者HSP包。

公共能力层

公共能力层的各子目录将编译成HAR包,仅产品定制层和基础特性层可依赖,不允许反向依赖。该层提取模块化公共基础能力,为上层提供标准接口和协议,提高复用率和开发效率。

应用分层架构实现

img


此次我们的组件封装就是在common文件中的compontHAR静态共享包中,需要将自定义组件在HAR的index.ets中导出组件,在使用方的oh-package.json5文件中引入

img

三、公用组件封装

基础组件封装

场景描述
在应用开发过程中,不同的业务场景可能需要使用相同功能或样式的ArkUI组件。例如,登录页面登录按钮和购物页面结算按钮可能样式相同。该场景常用方法是抽取相同样式的逻辑部分,并将其封装成一个自定义组件到公共组件库中。在业务场景开发时,统一从公共组件库获取封装好的公用组件。
ArkTS为每个系统组件提供了attributeModifier属性方法。该方法将组件属性设置分离到系统提供的AttributeModifier接口实现类实例中,通过自定义Class类实现AttributeModifier接口对系统组件属性进行扩展.

使用wrapBuilder方法有以下限制:

wrapBuilder方法只支持传入全局@Builder方法。
wrapBuilder方法返回的WrappedBuilder对象的builder属性方法只能在struct内部使用。

/*** @FileName : BaseButtonAttributeModifier* @Author : kirk.wang* @Time : 2025/5/16 10:23* @Description : 基础组件Button封装*/
//创建AttributeModifier接口的实现类
export class BaseButtonAttributeModifier implements AttributeModifier<ButtonAttribute> {// 私有定义Button组件特有属性private _buttonType: ButtonType = ButtonType.Normal;private _enabled: boolean = false;// 实现组件的普通状态下的样式方法,系统还提供了hover状态和其他状态下的样式方法applyNormalAttribute(instance: ButtonAttribute): void {instance.type(this._buttonType).stateEffect(true).fontSize(16).height(48).enabled(this._enabled)}// 链式配置方法type(buttonType: ButtonType): BaseButtonAttributeModifier {this._buttonType = buttonType;return this;}enabled(enabled: boolean): BaseButtonAttributeModifier {this._enabled = enabled;return this;}
}

使用方创建提供方的AttributeModifier实现类实例,并作为系统组件attributeModifier属性方法的参数传入。

/*** @FileName : GQButton* @Author : kirk.wang* @Time : 2025/5/16 15:35* @Description : 自定义Button组件*/
// src/main/ets/pages/CommonComponent.ets
import { BaseButtonAttributeModifier } from "./BaseButtonAttributeModifier";//提供方自定义组件并导出
@Component
export struct GQButton {@Prop text: string = '';// 接受外部传入的AttributeModifier类实例@Prop modifier: BaseButtonAttributeModifier;build() {Button(this.text)// 将入参的AttributeModifier类实例与系统组件绑定.attributeModifier(this.modifier).fontSize(20).width(200).height(50)}
}

通过以上方案,使组件封装更灵活,也保持了与系统组件一致的使用方式。

组合组件封装

在实际应用开发中,会有需要抽取复用的组件为多个系统组件的组合是情况,比如新闻APP,新闻列表的item就需要经常被使用,就需要封装一个。
封装自定义组件MediaCard并导出:

/*** @FileName : MediaCard* @Author : kirk.wang* @Time : 2025/5/16 15:05* @Description : 复合组件封装-图片文本组件*/
@Component
export struct MediaCard {@Prop imageSrc: PixelMap | ResourceStr | DrawableDescriptor;@Prop title: string;@Prop content: string;@Prop imageModifier: AttributeModifier<ImageAttribute>;@Prop titleModifier: AttributeModifier<TextAttribute>;@Prop contentModifier: AttributeModifier<TextAttribute>;build() {Column({ space: 8 }) {Image(this.imageSrc).attributeModifier(this.imageModifier)Text(this.title).attributeModifier(this.titleModifier)Text(this.content).attributeModifier(this.contentModifier)}.padding(12).backgroundColor(Color.White).borderRadius(8)}
}export class ImageModifier implements AttributeModifier<ImageAttribute> {// 实现 AttributeModifier 接口private imageWidth: Length = 0;private imageHeight: Length = 0;constructor(width: Length, height: Length) {this.imageWidth = width;this.imageHeight = height;}width(width: Length) {this.imageWidth = width;return this;}height(height: Length) {this.imageHeight = height;return this;}applyNormalAttribute(instance: ImageAttribute): void {instance.width(this.imageWidth);instance.height(this.imageHeight);instance.borderRadius($r('app.float.padding_borderRadius'))}
}export class TextModifier {private _fontSize: number | string | Resource| null = 14;constructor(fontSize?: number | string | Resource) {if(fontSize!==null){this._fontSize = fontSize!;}}fontSize(fontSize: number | string | Resource): TextModifier {this._fontSize= fontSize;return this;}applyNormalAttribute(instance: TextAttribute): void {instance.fontSize(this._fontSize);}
}

导出自定义组件:

export { MediaCard,TextModifier,ImageModifier } from './src/main/ets/components/MediaCard';

使用:

import {BaseButtonAttributeModifier,GQButton,MediaCard,ImageModifier,TextModifier,DialogUtils,factoryMap
} from "component";
@Component
export struct HomePage {build() {NavDestination() {MediaCard({imageModifier: this.imagesAttribute,titleModifier: this.titleAttribute,contentModifier: this.contentAttribute,imageSrc: $r('app.media.icon_header'),title: 'title',content: 'content'})
}
```ets
### 自定义弹窗封装1. 使用方通过全局 @Builder 封装弹窗结构,定义弹窗的内容和样式。
2. 提供方通过 UIContext 获取 PromptAction 对象,封装弹窗工具类。
3. 提供方创建打开和关闭弹窗的接口,使用方通过调用这些接口实现弹窗的显示和隐藏。以下是弹窗工具类实现代码:```ets
// 提供方封装的弹窗工具类
/*** @FileName : DialogUtils* @Author : kirk.wang* @Time : 2025/5/16 16:25* @Description : 封装弹窗的工具类*/
import { ComponentContent, promptAction } from '@kit.ArkUI';
export class DialogUtils{private static dialog:DialogUtils;private data:PopViewModel[] = new Array<PopViewModel>;static getInstance(): DialogUtils {if (!DialogUtils.dialog) {DialogUtils.dialog = new DialogUtils();}return DialogUtils.dialog;}//通过openCustomDialog创建打开弹窗的showDialog函数。static showDialog<T extends object>(type: PopViewShowType, contentView: WrappedBuilder<[T]>, args: T,options?: promptAction.BaseDialogOptions):void {let uiContext = AppStorage.get<UIContext>('uiContext');if (uiContext) {// The promptAction object was obtained.let prompt = uiContext.getPromptAction();let componentContent = new ComponentContent(uiContext, contentView, args);let customOptions: promptAction.BaseDialogOptions = {alignment: options?.alignment || DialogAlignment.Bottom};// Open pop-ups using openCustomDialogprompt.openCustomDialog(componentContent, customOptions);let data = DialogUtils.getInstance().data;let info: PopViewModel = {com: componentContent,popType: type};data[0] = info;}}
//通过closeCustomDialog创建关闭弹窗的closeDialog函数static closeDialog(popType:PopViewShowType):void{let context = AppStorage.get<UIContext>('uiContext');if(context){let prompt = context.getPromptAction();let sameTypeList = DialogUtils.getInstance().data.filter((model) => {return model.popType === popType;})let info = sameTypeList[sameTypeList.length - 1];if (info.com) {DialogUtils.getInstance().data = DialogUtils.getInstance().data.filter((model) => {return model.com !== info.com;})prompt.closeCustomDialog(info.com);}}}static showPopView<T extends object>(contentView: WrappedBuilder<[T]>, args: T,options?: promptAction.BaseDialogOptions):void {DialogUtils.showDialog(PopViewShowType.OPEN, contentView, args, options);}static closePopView():void {DialogUtils.closeDialog(PopViewShowType.OPEN);}
}
interface PopViewModel {com: ComponentContent<object>;popType: PopViewShowType;
}export enum PopViewShowType {OPEN
}
//使用DialogUtils.showPopView<Object>(wrapBuilder(buildDialogView), new Object(),{ alignment: DialogAlignment.Center });
//构建buildDialogView
@Builder
export function buildDialogView(_obj: Object) {Column({ space: 16 }) {Text($r('app.string.tips')).fontSize(16).fontWeight(FontWeight.Bold)Text($r('app.string.content')).fontSize(16)Row() {Button($r('app.string.cancel')).fontColor(Color.Blue).backgroundColor(Color.White).margin({ right: 10 }).width('42%').onClick(() => {DialogUtils.closePopView();})Button($r('app.string.confirm')).width('42%').onClick(() => {DialogUtils.closePopView();})}.justifyContent(FlexAlign.Center).width(328)}.padding(18).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).backgroundColor(Color.White).borderRadius($r('app.float.border_radius'))
}

弹窗组件的封装更加简洁高效,使用方可以快速实现自定义弹窗。

三、组件工厂类封装

当需要为不同的业务需求提供多种组件时,组件工厂类封装是一种有效的解决方案。

实现流程优化

  1. 在组件工厂实现方,通过全局 @Builder 方法封装需要工厂化的组件。

  2. 将封装好的 @Builder 方法使用 wrapBuilder 函数包裹,并存入 Map 结构中,其中 key 为组件名,value 为 WrappedBuilder 对象。

  3. 在使用方,通过组件工厂的 key 值获取对应的 WrappedBuilder 对象,并在 build 方法中调用其 builder 属性方法获取组件。
    以选择月份的组件为例,项目种有多个地方需要选择月份,那么以下是组件工程封装的实现代码:

// 组件工厂实现方
/*** @FileName : FactoryMap* @Author : kirk.wang* @Time : 2025/5/18 00:50* @Description :月份选择组件*/
let mothList = ["1","2","3","4","5","6","7","8","9","10","11","12"];
@Builder
function monthRadio() {Text($r('app.string.month')).width('100%').fontColor($r('sys.color.mask_secondary'))List(){ForEach(mothList, (item: string, index: number) => {ListItem(){Row() {Radio({ value: `${index}`, group: 'radioGroup' })Text(`${item}月`)}.width('100%')}});}}// 创建组件工厂的集合
let factoryMap: Map<string, object> = new Map();//把需要的工厂存储在组件工厂中的组件。
factoryMap.set('monthRadio', wrapBuilder(monthRadio));// Export assembly factory
export { factoryMap };//使用 HomePage.ets
import { factoryMap} from "component";@Component
export struct HomePage {monthRadio: WrappedBuilder<[]> = factoryMap.get('monthRadio') as WrappedBuilder<[]>;build() {NavDestination() {this.monthRadio.builder();}
}

四、总结与展望

在鸿蒙应用分层架构下进行 ArkUI 组件封装,可以提高开发效率和代码复用率。通过合理利用 ArkTS 提供的 attributeModifier 属性方法、PromptAction 对象以及组件工厂模式,可以实现灵活、高效的组件封装。未来,随着鸿蒙应用生态的发展,组件封装技术将不断完善。开发者们可以探索更多创新的封装方式,为鸿蒙应用开发提供更强大的支持。

四、Demo代码仓

https://gitcode.com/kirkWang/componentMaster

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

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

相关文章

JAVA请求vllm的api服务报错Unsupported upgrade request、 Invalid HTTP request received.

环境&#xff1a; vllm 0.8.5 java 17 Qwen3-32B-FP8 问题描述&#xff1a; JAVA请求vllm的api服务报错Unsupported upgrade request、 Invalid HTTP request received. WARNING: Unsupported upgrade request. INFO: - "POST /v1/chat/completions HTTP/1.1&…

旧 docker 版本通过 nvkind 搭建虚拟多节点 gpu 集群的坑

踩坑 参考nvkind教程安装到Setup这一步&#xff0c;由于docker版本较旧&#xff0c;–cdi.enabled 和 config 参数执行不了 手动修改 /etc/docker/daemon.json 配置文件 "features": {"cdi": true}手动修改 /etc/nvidia-container-runtime/config.toml 配…

C++:与7无关的数

【描述】 一个正整数,如果它能被7整除,或者它的十进制表示法中某一位上的数字为7,则称其为与7相关的数.现求所有小于等于n(n < 100)的与7无关的正整数的平方和. 【输入】 输入为一行,正整数n(n < 100) 【输出】 输出一行&#xff0c;包含一个整数&#xff0c;即小于等于n…

FPGA:Lattice的FPGA产品线以及器件选型建议

本文将详细介绍Lattice Semiconductor的FPGA产品线&#xff0c;帮助你了解各系列的特点和适用场景&#xff0c;以便更好地进行选型。Lattice以低功耗、小尺寸和高性能为核心&#xff0c;产品覆盖低中端市场&#xff0c;广泛应用于通信、计算、工业、汽车、消费电子、嵌入式视觉…

汽车零部件冲压车间MES一体机解决方案

在当前制造业升级的大背景下&#xff0c;提升生产效率、实现精细化管理已成为企业竞争力的关键。特别是在汽车零部件制造领域&#xff0c;冲压车间作为生产流程中的重要一环&#xff0c;其生产数据的实时采集与分析对于确保产品质量、优化生产节拍、降低运营成本至关重要。今天…

32、跨平台咒语—— React Native初探

一、时空晶体架构&#xff08;核心原理&#xff09; 1. 量子组件桥接协议 // 原生组件映射 <View> → iOS UIView / Android ViewGroup <Text> → UILabel / TextView 魔法特性&#xff1a; • JavaScriptCore引擎&#xff1a;通过V8/Hermes引擎执行JS逻辑…

前端面试宝典---webpack面试题

webpack 的 tree shaking 的原理 Webpack 的 Tree Shaking 过程主要包含以下步骤&#xff1a; 模块依赖分析&#xff1a;Webpack 首先构建一个完整的模块依赖图&#xff0c;确定每个模块之间的依赖关系。导出值分析&#xff1a;通过分析模块之间的 import 和 export&#xff…

VUE3_ref和useTemplateRef获取组件实例,ref获取dom对象

旧写法 ref的字符串需要跟js中ref定义的变量名称一样 类型丢失&#xff0c;无法获取到ref定义的title类型 <template><div><h1 ref"title">Hello Vue3.5</h1></div> </template><script setup>import { ref, onMounted } …

知识图谱(KG)与大语言模型(LLM)

知识图谱&#xff08;KG&#xff09;以其结构化的知识表示和推理能力&#xff0c;为大语言模型&#xff08;LLM&#xff09;的“幻觉”、知识更新滞后和可解释性不足等问题提供了有力的解决方案。反过来&#xff0c;LLM的强大文本理解和生成能力也为KG的构建、补全、查询和应用…

MySQL数据库设计

1. 如何设计数据库 设计数据库步骤 2. E-R图的使用 我们在日常设计的数据库多为“一对多”和“多对一” 3. 设计数据库三大范式⭐ 第一范式&#xff08;1st NF&#xff09;&#xff1a;确保每列的原子性 第二范式&#xff08;2st NF&#xff09;&#xff1a;每个表只描述一件事…

C#中Action的用法

Action 是 C# 中委托的一种&#xff0c;用于封装无返回值的方法。它引用的方法不能有返回值&#xff0c;但可以有零个或多个参数。相比delegate委托&#xff0c;Action 委托的优点是不必显式定义封装无参数过程的委托&#xff0c;使代码更加简洁和易读。 1、delegate-委托 先…

计算机视觉与深度学习 | matlab实现EMD-CNN-LSTM时间序列预测(完整源码、数据、公式)

EMD-CNN-LSTM 一、完整代码实现二、核心公式说明1. **经验模态分解(EMD)**2. **1D卷积运算**3. **LSTM门控机制**4. **损失函数**三、代码结构解析四、关键参数说明五、性能优化建议六、典型输出示例以下是用MATLAB实现EMD-CNN-LSTM时间序列预测的完整方案,包含数据生成、经…

mybatis-plus实操

如何生成完全看项目&#xff0c;有的人是用管理系统生成&#xff0c;还有其他人可能是.....。博主这里是用插件生成 我是插件的话&#xff0c;先在ide连接上数据源&#xff0c;然后对表右键&#xff0c;直接来到下面这个步骤&#xff0c; 第一次是新增6个文件&#xff0c;我们…

Mergekit——任务向量合并算法Ties解析

Mergekit——高频合并算法 TIES解析 Ties背景Ties 核心思想具体流程总结 mergekit项目地址 Mergekit提供模型合并方法可以概况为三大类&#xff1a;基本线性加权、基于球面插值、基于任务向量&#xff0c;今天我们来刷下基于任务向量的ties合并方法&#xff0c;熟悉原理和代码。…

YOLOv8 在单片机上部署的缺点和应对方案

YOLOv8 在单片机上部署的主要挑战与缺陷 将 YOLOv8 部署到单片机上确实面临诸多技术挑战&#xff0c;主要源于单片机有限的计算资源与 YOLOv8 模型的高复杂度之间的矛盾。以下是具体的缺陷和限制&#xff1a; 1. 计算资源严重不足 算力限制&#xff1a;典型单片机&#xff0…

搭建一个永久免费的博客

搭建永久免费的博客&#xff08;1&#xff09;基本介绍 HugoStackGitHub GitHub GitHub GitHub Build and ship software on a single, collaborative platform GitHub 下载安装git Git - Downloads Edge插件authenticator 2fa client Settings->Password and auth…

基于SpringBoot的小型民营加油站管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

推荐一个Winform开源的UI工具包

从零学习构建一个完整的系统 推荐一个开源、免费的适合.NET WinForms 控件的套件。 项目简介 Krypton是一套开源的.Net组件&#xff0c;用于快速构建具有丰富UI交互的WinForms应用程序。 丰富的UI控件&#xff0c;提供了48个基础控件&#xff0c;如按钮、文本框、标签、下拉…

社交平台推出IP关联机制:增强用户体验与网络安全的新举措

社交平台为我们提供与亲朋好友保持联系、分享生活点滴的便捷渠道&#xff0c;还成为了信息传播、观点交流的重要平台。然而&#xff0c;随着社交平台的普及&#xff0c;网络空间中的虚假信息、恶意行为等问题也日益凸显。为了应对这些挑战&#xff0c;许多社交平台相继推出IP关…

系统架构设计(八):三层架构

什么是三层架构&#xff08;Three-Tier Architecture&#xff09; 三层架构是将系统分为三大逻辑层&#xff1a;表示层&#xff08;Presentation&#xff09;、业务逻辑层&#xff08;Business Logic&#xff09;、数据访问层&#xff08;Data Access&#xff09;&#xff0c;…