• Lv2
    粉丝0

积分23 / 贡献0

提问0答案被采纳0文章1

作者动态

    [经验分享] HCompass -- 像搭积木一样构建鸿蒙应用

    groot 显示全部楼层 发表于 7 天前
    ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtybbs/419/088/345/2850086000419088345.20260224213823.00577084961998626942603746830004:50001231000000:2800:9805FBE8783F59E43DAF17D601935984886DE2DDAEF1032F7CE0FDAC067693BA.png)

    > 模块化、可复用、开源的 HarmonyOS 快速开发框架

    ---

    ## 为什么需要 HCompass?

    鸿蒙生态正在高速发展,越来越多的开发者投入到 HarmonyOS 应用开发中。但在实际项目中,我们常常面临这些痛点:

    - 每个新项目都要从零搭建基础架构,重复造轮子
    - 网络请求、路由导航、状态管理等通用能力缺乏统一封装
    - 多人协作时代码风格不一致,模块边界模糊
    - 业务功能难以跨项目复用,积累的经验无法沉淀

    **HCompass** 正是为解决这些问题而生。它不是一个 UI 组件库,而是一套完整的应用开发框架 -- 帮你把地基打好,让你专注于业务本身。

    ---

    ## 核心理念:功能包即积木

    HCompass 的核心思想很简单:**把应用拆成一个个独立的"功能包",像搭积木一样组合它们。**

    每个功能包都是一个自包含的业务模块,拥有自己的页面、ViewModel、服务和数据模型。需要登录功能?放入 `auth` 功能包。需要用户中心?放入 `user` 功能包。功能包之间通过契约层解耦,互不依赖,随时可以拆卸和替换。

    ```
    你的应用 = Entry(入口) + 若干功能包(积木)
    ```

    ---

    ## 四层架构,职责分明

    HCompass 采用清晰的四层架构设计:
    ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtybbs/419/088/345/2850086000419088345.20260224212941.37268672459696843230556545226026:50001231000000:2800:BC5790C0A083854457D04422771E0C34035E0941DDD2D22C34B0BBC9F5703DF6.png)

    | 层级               | 职责       | 关键特征                                   |
    | ------------------ | ---------- | ------------------------------------------ |
    | **Entry**    | 应用入口   | 初始化框架、注册功能包、配置路由           |
    | **Packages** | 业务功能包 | 独立开发、独立测试、可跨项目复用           |
    | **Shared**   | 共享契约   | 定义服务接口和类型,功能包之间的"协议"     |
    | **Core**     | 框架核心   | 与业务无关的通用能力,可直接迁移到任何项目 |

    这种分层设计带来的好处是:**Core 层可以直接复用到你的下一个项目,Packages 层的功能包可以按需组合,Shared 层确保模块之间的通信有据可依。**

    ---

    ## 开箱即用的核心能力

    ### 依赖注入(DI)

    轻量级 DI 容器,让功能包之间彻底解耦:

    ```typescript
    // 在 Shared 层定义契约
    interface IUserRepository {
      getUserInfo(id: string): Promise<User>;
    }

    // 在功能包中实现并注册
    register<IUserRepository>(ServiceKeys.USER_REPO, new UserRepositoryImpl());

    // 在任何地方解析使用
    const userRepo = resolve<IUserRepository>(ServiceKeys.USER_REPO);
    ```

    不再需要硬编码依赖关系,功能包只依赖契约,不依赖具体实现。

    ### 导航系统

    统一的路由管理,支持路由守卫:

    ```typescript
    // 路由跳转
    NavigationService.push(UserRoutes.PROFILE, { userId: "123" });

    // 路由守卫 -- 未登录自动跳转登录页
    GuardManager.addGuard(new LoginGuard());
    ```

    支持登录守卫、权限守卫、条件守卫,灵活组合,按优先级执行。

    ### 网络请求

    基于 Axios 封装,告别重复的请求模板代码:

    ```typescript
    // 继承 BaseNetWorkViewModel,自动管理 loading/error/success 状态
    @ObservedV2
    class UserProfileViewModel extends BaseNetWorkViewModel<User> {
      async fetchData(): Promise<void> {
        await this.request(() => this.userRepo.getUserInfo(this.userId));
      }
    }
    ```

    内置拦截器链、统一错误处理、分页逻辑,你只需关注业务数据。

    ### 设计系统

    统一的设计令牌,确保 UI 一致性:

    ```typescript
    // 百分比常量,告别魔法数字
    Row() { ... }.width(P100).height(P50)

    // 间距组件,统一视觉节奏
    Column() {
      Text("标题")
      SpaceVerticalMedium()  // 12vp 间距
      Text("内容")
    }
    ```

    ### 基础父类

    三个 ViewModel 基类覆盖 90% 的页面场景:

    | 基类                         | 适用场景                                     |
    | ---------------------------- | -------------------------------------------- |
    | `BaseViewModel`            | 通用页面,提供生命周期管理                   |
    | `BaseNetWorkViewModel`     | 网络请求页面,自动处理 loading/error/success |
    | `BaseNetWorkListViewModel` | 分页列表页面,内置下拉刷新和上拉加载         |

    ---

    ## 实战案例:聚合登录功能包

    光说架构不够直观,我们直接看官方实现的 `login` 功能包 -- 一个支持华为账号一键登录、微信、支付宝、短信验证码的聚合登录模块,完整展示了功能包从契约定义到页面渲染的全流程。

    ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtybbs/419/088/345/2850086000419088345.20260224213144.73209419816921004296753072602726:50001231000000:2800:C01C7535E00E4EAF80A0349DDCAD94157308224511B29534ED123EA5E0FB6D09.png)

    ### 功能包目录结构

    ```
    packages/login/
    ├── LoginModule.ets                    # 模块生命周期(DI注册 + 路由 + 守卫)
    ├── view/
    │   ├── LoginPage.ets                  # 主登录页(华为一键登录 + 三方登录)
    │   └── SmsLoginPage.ets               # 短信验证码登录页
    ├── viewmodel/
    │   ├── LoginViewModel.ets             # 登录业务逻辑
    │   └── SmsLoginViewModel.ets          # 短信登录逻辑
    ├── components/
    │   ├── AnimatedAuthPage.ets           # 认证页面基础布局
    │   ├── PhoneInputField.ets            # 手机号输入组件
    │   ├── VerificationCodeField.ets      # 验证码输入组件
    │   ├── UserAgreement.ets              # 用户协议组件
    │   └── ...
    ├── navigation/
    │   ├── LoginNav.ets                   # 登录页导航构建器
    │   └── SmsLoginNav.ets                # 短信登录页导航构建器
    ├── services/
    │   └── AuthNavSvcImpl.ets             # 导航服务实现
    └── models/
        └── Constant.ets                   # 三方 APP_ID 配置
    ```

    View / ViewModel / Service / Navigation 各司其职,结构一目了然。

    ### 第一步:在 Shared 层定义契约

    功能包对外暴露的能力,全部通过 Shared 层的接口契约来声明:

    ```typescript
    // shared/contracts -- 导航服务契约
    export const AUTH_NAV_SVC_KEY: string = "authNavService";

    export interface IAuthNavSvc {
      toLogin(): void;       // 跳转登录页
      toSmsLogin(): void;    // 跳转短信登录页
    }

    // shared/contracts -- 路由常量
    export class AuthRoutes {
      static Login = "auth/login";
      static SmsLogin = "auth/sms-login";
    }
    ```

    其他功能包只需要依赖这些接口,不需要知道登录页长什么样、用了哪个 SDK。

    ### 第二步:注册模块 -- 一个类搞定 DI、路由、守卫

    `LoginModule` 实现 `FeatureModule` 接口,框架会在启动时自动调用:

    ```typescript
    export class LoginModule implements FeatureModule {
      readonly moduleId: string = 'auth';
      readonly moduleName: string = '认证模块';
      readonly version: string = '1.0.0';
      readonly dependencies: string[] = [];

      // 注册服务到 DI 容器
      registerServices(container: Container): void {
        container.register<IAuthNavSvc>(AUTH_NAV_SVC_KEY, () => new AuthNavSvcImpl());
      }

      // 注册页面路由
      registerRoutes(registry: RouteRegistry): void {
        registry.register(AuthRoutes.Login, loginNavBuilderWrapper);
        registry.register(AuthRoutes.SmsLogin, smsLoginNavBuilderWrapper);
      }

      // 注册路由守卫 -- 未登录自动拦截
      registerGuards(navigationService: NavigationService): void {
        navigationService.registerGuard(new AuthGuard());
      }
    }
    ```

    三个方法,把服务注入、路由注册、登录守卫全部声明完毕。当用户访问受保护的页面时,`AuthGuard` 会自动检查登录状态,未登录则跳转到登录页:

    ```typescript
    class AuthGuard implements RouteGuard {
      readonly name: string = 'AuthGuard';
      readonly priority: number = 100;  // 优先级最高

      canActivate(context: RouteContext): boolean {
        return getUserState().isLoggedIn();  // 已登录放行,未登录拦截
      }

      onReject(context: RouteContext): void {
        // 拦截后自动跳转登录页
        navigation?.navigateTo(AuthRoutes.Login, context.params);
      }
    }
    ```

    ### 第三步:ViewModel 封装业务逻辑

    `LoginViewModel` 继承 `BaseViewModel`,集中处理多种登录方式:

    ```typescript
    @ObservedV2
    export default class LoginViewModel extends BaseViewModel {
      @Trace anonymousPhone: string = "";  // 华为账号匿名手机号

      // 华为账号一键登录控制器
      huaweiLoginController: LoginWithHuaweiIDButtonController =
        new LoginWithHuaweiIDButtonController()
          .setAgreementStatus(AgreementStatus.ACCEPTED)
          .onClickLoginWithHuaweiIDButton((error, response) => {
            this.handleLoginWithHuaweiIDButton(error, response);
          });

      // 微信 OAuth 登录
      async onWechatLoginClick(): Promise<void> {
        const share: ShareWxSdk = new ShareWxSdk(APP_ID_WX);
        const res = await share.wechatOAuth("snsapi_userinfo", state, () => {});
        if (res instanceof SendAuthResp) {
          const userInfo = await share.getUserInfo(APP_SECRET_WX, res);
          // 处理登录结果...
        }
      }

      // 支付宝授权登录
      onAlipayLoginClick(): void {
        AFServiceCenter.call(AFService.AFServiceAuth, params);
      }
    }
    ```

    View 层不包含任何业务逻辑,只负责绑定 ViewModel 的状态和方法。

    ### 第四步:View 层 -- 声明式 UI 渲染

    ```typescript
    @ComponentV2
    export struct LoginPage {
      @Local private vm: LoginViewModel = new LoginViewModel();

      build(): void {
        AppNavDestination({ viewModel: this.vm }) {
          // Logo
          LogoIcon()

          // 匿名手机号展示
          Text(this.vm.anonymousPhone)

          // 华为账号一键登录按钮
          LoginWithHuaweiIDButton({
            params: { style: Style.BUTTON_CUSTOM, loginType: LoginType.QUICK_LOGIN },
            controller: this.vm.huaweiLoginController,
          })

          // 短信验证码登录
          IBestButton({
            text: $r("app.string.sms_login"),
            onBtnClick: () => {
              getContainer().tryResolve<IAuthNavSvc>(AUTH_NAV_SVC_KEY)?.toSmsLogin();
            }
          })

          // 第三方登录(微信 / 支付宝 / QQ)
          this.buildThirdPartyLogin()

          // 用户协议
          UserAgreement()
        }
      }
    }
    ```

    注意跳转短信登录页时,通过 DI 容器解析 `IAuthNavSvc` 服务来导航,而不是直接引用目标页面 -- 这就是契约解耦的威力。

    ### 第五步:可复用组件沉淀

    login 功能包还沉淀了一组可复用的认证 UI 组件:

    ```typescript
    // AnimatedAuthPage -- 认证页面通用布局,接收自定义内容
    @ComponentV2
    export struct AnimatedAuthPage {
      @Param title: ResourceStr = "";
      @BuilderParam content: CustomBuilder;

      build(): void {
        LargePaddingVerticalScroll({ fillMaxSize: true }) {
          Text(this.title).fontSize(28).fontWeight(FontWeight.Medium)
          SpaceVerticalXXLarge()
          if (this.content) { this.content(); }
        }
      }
    }

    // 短信登录页直接复用这个布局
    AnimatedAuthPage({ title: $r("app.string.welcome_login") }) {
      PhoneInputField({ ... })
      VerificationCodeField({ ... })
      UserAgreement()
      IBestButton({ text: $r("app.string.login"), ... })
    }
    ```

    `PhoneInputField`、`VerificationCodeField`、`UserAgreement` 这些组件,在你开发注册页、找回密码页时可以直接复用。

    ### 小结:一个功能包的完整生命周期

    ```
    Shared 层定义契约(接口 + 路由 + 类型)
            |
    LoginModule 注册服务、路由、守卫
            |
    ViewModel 封装业务逻辑(华为/微信/支付宝/短信)
            |
    View 层声明式渲染(绑定 ViewModel,零业务代码)
            |
    可复用组件沉淀(AnimatedAuthPage / PhoneInputField / ...)
    ```

    这就是 HCompass 功能包的开发范式。每个功能包都遵循同样的模式,新成员看一个包就能上手所有包。

    ---

    ## 技术栈

    | 技术                       | 用途               |
    | -------------------------- | ------------------ |
    | HarmonyOS NEXT             | 开发平台           |
    | ArkTS / ArkUI              | 开发语言与 UI 框架 |
    | @ohos/axios                | HTTP 请求          |
    | @ibestservices/ibest-ui-v2 | UI 组件库          |
    | @ibestservices/ibest-orm   | 数据库 ORM         |

    ---

    ## 谁适合使用 HCompass?

    - **独立开发者**:快速搭建应用骨架,把精力放在业务创新上
    - **创业团队**:统一技术栈和代码规范,降低协作成本
    - **企业开发团队**:沉淀业务功能包,跨项目复用,提升交付效率
    - **鸿蒙生态贡献者**:以功能包为单位贡献开源模块,共建生态

    ---

    ## 完善的文档体系

    HCompass 提供了基于 VitePress 构建的完整文档站点,涵盖:

    - **快速开始** -- 从零搭建你的第一个 HCompass 应用
    - **架构设计** -- 深入理解四层架构和 MVVM 模式
    - **核心模块** -- DI、导航、网络、组件等每个模块的详细 API 文档
    - **功能包开发** -- 手把手教你创建和发布功能包
    - **最佳实践** -- 代码规范、性能优化、安全指南、状态管理

    文档地址:[https://hcompass.codelably.com](https://hcompass.codelably.com)

    ---

    ## 快速开始

    ```bash
    # 克隆项目
    git clone https://github.com/codelably/HCompass.git

    # 使用 DevEco Studio 打开项目
    # 等待依赖同步完成
    # 运行 entry 模块即可体验
    ```

    环境要求:

    - DevEco Studio 5.0+
    - HarmonyOS SDK 最新稳定版
    - Node.js 18.0+

    ---

    ## 开源协议

    HCompass 基于 **MIT** 协议开源,你可以自由使用、修改和分发。

    欢迎通过 Issue 和 Pull Request 参与项目建设,一起让鸿蒙开发更简单。

    - GitHub:[https://github.com/codelably/HCompass](https://github.com/codelably/HCompass)
    - AtomGit:[https://atomgit.com/codelably/HCompass](https://atomgit.com/codelably/HCompass)
    - 文档:[https://hcompass.codelably.com](https://hcompass.codelably.com)

    ---

    ©著作权归作者所有,转载或内容合作请联系作者

    您尚未登录,无法参与评论,登录后可以:
    参与开源共建问题交流
    认同或收藏高质量问答
    获取积分成为开源共建先驱

    Copyright   ©2025  OpenHarmony开发者论坛  京ICP备2020036654号-3 | 京公网安备11030102011662号 |技术支持 Discuz!

    返回顶部