HarmonyOS 应用开发《掌上英语》第4篇:组件化设计原则:从 8 个业务组件看模块复用
2026/7/11 20:27:24 网站建设 项目流程

组件化设计原则:从 8 个业务组件看模块复用

一、引言

在 HarmonyOS 应用开发中,组件化是应对业务复杂度增长的核心手段。当项目从单模块向多模块演进时,如何设计高内聚、低耦合的业务组件,如何管理组件的构建与发布,成为架构师必须回答的问题。

本文以一个包含 11 个模块的 HarmonyOS 英语学习 App 为案例,深入分析其components/目录下 8 个独立业务组件的设计模式,涵盖独立模块配置、接口导出、状态边界控制和 HAR 包构建等关键主题。这 8 个组件分别是:

组件名称功能定位依赖关系
base_select基础选择器无依赖
select_category分类选择无依赖
answer_questions答题引擎无依赖
login_info登录模块无依赖
aggregated_payment聚合支付无依赖
feedback意见反馈依赖 axios、mock
search搜索 UI无依赖
search_question题目搜索无依赖

其中 7 个组件零外部依赖,体现了纯粹的组件化设计思想。


二、独立模块的 build-profile.json5 配置

每个组件都是一个独立的 HAR(Harmony Archive)模块,拥有自己的build-profile.json5文件。以base_select为例:

{ "apiType": "stageMode", "buildOption": {}, "buildOptionSet": [ { "name": "release", "arkOptions": { "obfuscation": { "ruleOptions": { "enable": false, "files": ["./obfuscation-rules.txt"] }, "consumerFiles": ["./consumer-rules.txt"] } } } ], "targets": [ { "name": "default" }, { "name": "ohosTest" } ] }

组件模块的配置要点如下:

1. apiType 固定为 stageMode
HarmonyOS 从 API 9 开始全面推行 Stage 模型,所有组件模块必须统一使用stageMode,确保生命周期管理和上下文传递的一致性。

2. consumerFiles 混淆规则
作为 HAR 包,组件需要提供consumer-rules.txt,将混淆规则传递给最终消费者。这确保了组件内部的代码经过混淆保护,同时对外暴露的接口保持可用。

3. 极简的 buildOption
组件模块无需关心签名、权限等应用级配置,因此buildOption通常为空。配置的简化也是组件化的优势之一——聚焦业务逻辑而非工程配置。

4. 项目级模块注册
在根目录build-profile.json5中,所有组件模块需要注册到modules数组:

{ "modules": [ { "name": "base_select", "srcPath": "./components/base_select" }, { "name": "select_category", "srcPath": "./components/select_category" }, { "name": "answer_questions", "srcPath": "./components/answer_questions" }, { "name": "login_info", "srcPath": "./components/login_info" }, { "name": "aggregated_payment", "srcPath": "./components/aggregated_payment" }, { "name": "feedback", "srcPath": "./components/feedback" }, { "name": "search", "srcPath": "./components/search" }, { "name": "search_question", "srcPath": "./components/search_question" } ] }

三、Index.ets 导出组件能力的模式

每个组件模块的Index.ets是模块对外的唯一接口门面(Facade),这是模块化设计的第一原则:接口先行,实现隐藏

3.1 简单导出:直接暴露组件

最简单的模式如search组件,仅导出一个页面组件:

// components/search/Index.etsexport{SearchPage}from'./src/main/ets/components/SearchPage';

search_question同样遵循此模式:

// components/search_question/Index.etsexport{SearchQuestionPage}from'./src/main/ets/components/SearchQuestionPage';

3.2 复合导出:组件 + 数据模型

更常见的做法是同时导出组件和数据模型,让消费者既能使用 UI 组件,也能引用类型定义。以base_select为例:

// components/base_select/Index.etsexport{BaseSelect,ContentItemModel,SelectItemModel,ThirdItemModel,SecondItemModel,ItemDetail,SecondParam}from'./src/main/ets/components/MainPage';

这里导出了 1 个组件和 6 个数据模型类型。消费者可以这样使用:

import{BaseSelect,SelectItemModel}from'base_select';

3.3 全量导出:复杂业务模块

对于像answer_questions这样功能丰富的组件,其 Index.ets 采用了全量导出策略:

// components/answer_questions/Index.etsexport*from'./src/main/ets/components/AnswerQuestionsPage';export*from'./src/main/ets/components/AddNotePage';export*from'./src/main/ets/components/AnswerSheetPage';export{TopicItemModel,AnswerItem,TopicItemModelPreference,AnswerItemPreference}from'./src/main/ets/viewModel/TopicItemModel';export{TopicItemType,AnswerItemType,QUESTION_DATA,WORD_QUESTION_DATA,LISTENING_QUESTION_DATA,READING_QUESTION_DATA,GRAMMAR_QUESTION_DATA,QuestionsRouterModel}from'./src/main/ets/viewModel/TopicPageModel';export{AnswerSheetDialog}from'./src/main/ets/dialog/AnswerSheetDialog';export{AddNoteDialog}from'./src/main/ets/dialog/AddNoteDialog';

这里导出了 3 个页面组件、2 个对话框、4 个数据模型和 7 个常量/枚举。特别值得注意的是QuestionsRouterModel——这是一个导航路由模型,表明该组件具备独立的路由能力,可以在内部管理自己的页面栈。

3.4 能力导出:服务 + 组件

aggregated_payment组件展示了服务能力的导出模式:

// components/aggregated_payment/Index.etsexport{PaymentCallback,PaymentType}from'./src/main/ets/common/CustomTypes';export{WechatPaymentInfo}from'./src/main/ets/models/WechatPaymentInfo';export{HuaweiPaymentInfo}from'./src/main/ets/models/HuaweiPaymentInfo';export{AlipayPaymentInfo}from'./src/main/ets/models/AlipayPaymentInfo';export{CashierPicker}from'./src/main/ets/CashierPicker';export{aggregatedPaymentService}from'./src/main/ets/AggregatedPaymentService';

不仅导出了 UI 组件CashierPicker,还导出了聚合支付服务aggregatedPaymentService和多种支付模型。这种模式下,组件不仅是 UI 的复用,更是业务能力的复用


四、组件内部状态与外部参数的边界

良好的组件化设计需要清晰划分"内部状态"和"外部参数"的边界。本项目使用 ArkTS V2 装饰器体系来实现这一划分。

4.1 @Param:外部传入的参数

login_info组件的QuickLogin为例:

@ComponentV2exportstruct QuickLogin{// 外部参数:由父组件传入@ParamisBindContentCover:boolean=false;@Param@Requireicon:ResourceStr;@Param@RequireloginBtnBgColor:string|ResourceStr;@Param@RequireappName:string='';// 事件回调:通知父组件@EventonLoginWithHuaweiID:(flag:boolean,phone?:string)=>void=()=>{};@EventonPrivacyPolicy:()=>void=()=>{};@EventonServicePolicy:()=>void=()=>{};@EventshouldDismiss:()=>void=()=>{};// 内部状态:组件自己管理@LocalisSelected:boolean=false;@LocalquickLoginAnonymousPhone:string='';}

设计原则清晰:

  • @Param标注外部注入的参数,使用@Require标记必填项
  • @Event标注事件回调,组件通过回调通知父组件状态变化
  • @Local标注内部状态,外部不可直接干涉

4.2 父组件如何使用

LoginPage中,组件的使用方式如下:

@ComponentV2exportstruct LoginPage{isBindContentCover:boolean=false;build(){Column(){QuickLogin({isBindContentCover:this.isBindContentCover,icon:$r('app.media.exam_icon_login'),loginBtnBgColor:'#4B5CC4',appName:'备考',onLoginWithHuaweiID:(flag:boolean,phone?:string)=>{// 处理登录成功逻辑letprop:UserInfo=AppStorageV2.connect(UserInfo,()=>newUserInfo())!;prop.isLogin=true;// ...},shouldDismiss:()=>{RouterModule.pop();}});}}}

父组件通过传参控制组件的行为,通过回调监听组件的事件,两者之间没有直接的状态共享,实现了彻底的解耦。

4.3 参数设计最佳实践

  1. 必填参数用 @Require:编译期检查,防止遗漏
  2. 提供合理的默认值:非必填参数给出默认值,降低使用成本
  3. 回调用 @Event:语义清晰,区别于 @Param
  4. 内部状态用 @Local:外部不可修改,保持封装性
  5. 复杂参数使用接口类型:提高可读性和类型安全

五、HAR 包的构建与引用

5.1 构建 HAR 包

HAR(Harmony Archive)是 HarmonyOS 的静态共享包格式,类似于 Android 的 AAR。当使用hvigor构建时,组件模块默认输出.har文件:

components/base_select/build/default/outputs/default/base_select.har

HAR 包的特点:

  • 包含编译后的字节码和资源文件
  • 不包含签名信息,由最终应用签名
  • 支持混淆(通过 consumer-rules.txt)
  • 支持资源合并

5.2 引用方式

在消费者模块的oh-package.json5中通过文件路径引用:

// product/entry/oh-package.json5 { "dependencies": { "login_info": "file:../../components/login_info", "answer_questions": "file:../../components/answer_questions", "feedback": "file:../../components/feedback" } }

homePagefeature 模块则引用了更多组件:

// features/homePage/oh-package.json5 { "dependencies": { "commonlib": "file:../../commons/commonLib", "login_info": "file:../../components/login_info", "search": "file:../../components/search", "search_question": "file:../../components/search_question", "answer_questions": "file:../../components/answer_questions", "base_select": "file:../../components/base_select", "select_category": "file:../../components/select_category" } }

5.3 独立组件的依赖策略

值得关注的是,8 个组件中仅有feedback组件具有外部依赖:

// components/feedback/oh-package.json5 { "dependencies": { "@ohos/axios": "2.2.6", "@hw-agconnect/axios-mock-adapter": "1.0.0", "@hw-agconnect/util-log": "1.0.0", "@hw-agconnect/ui-toast": "1.0.0" } }

base_selectanswer_questionslogin_info等 7 个组件的依赖为空:

// components/base_select/oh-package.json5 { "dependencies": {} }

这种零依赖设计是组件复用的最高境界——组件不依赖任何外部库或框架,可以嫁接到任何项目中。实际开发中应尽可能保持组件的纯净性,将外部依赖降到最低。


六、最佳实践总结

1. 独立配置,独立构建
每个组件拥有自己的build-profile.json5oh-package.json5hvigorfile.ts,实现构建层面的完全独立。

2. Index.ets 作为唯一出口
所有对外暴露的能力通过 Index.ets 导出,消费者禁止引用模块内部路径。

3. 清晰的装饰器语义

  • @Param:外部传入参数
  • @Event:事件回调
  • @Local:内部状态
  • @Require:必填标记

4. 最小依赖原则
组件应尽可能零外部依赖,必须的依赖通过oh-package.json5声明并由消费者传递。

5. HAR 包的版本管理
每个组件维护独立的CHANGELOG.mdLICENSE,支持独立的版本迭代和发布。

通过以上设计,本项目实现了组件的最大化复用——同一个组件可以服务于多个 feature 模块,甚至可以跨项目复用。当新业务需要类似功能时,只需要引入对应的 HAR 包并传入参数即可,无需重复开发。


七、参考源码路径

文件路径
根模块配置d:\HarmonyOS\WorkSpace\Exam1.0.4\build-profile.json5
base_select 模块d:\HarmonyOS\WorkSpace\Exam1.0.4\components\base_select\
answer_questions 模块d:\HarmonyOS\WorkSpace\Exam1.0.4\components\answer_questions\
login_info 模块d:\HarmonyOS\WorkSpace\Exam1.0.4\components\login_info\
feedback 模块d:\HarmonyOS\WorkSpace\Exam1.0.4\components\feedback\
aggregated_payment 模块d:\HarmonyOS\WorkSpace\Exam1.0.4\components\aggregated_payment\

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询