在这里插入图片描述

前言

HarmonyOS NEXT(API 12+)的生态正以前所未有的速度繁荣发展,其中三方库是提升开发效率的关键一环。与传统的 Android 或 iOS 开发不同,鸿蒙生态拥有自己独立的包管理工具——ohpm(OpenHarmony Package Manager)。许多从其他平台转来的开发者习惯性地使用 npm,却在鸿蒙项目中频频碰壁,原因正是混淆了 npm 与 ohpm 的边界。

本文将围绕三个核心维度展开:第一,ohpm 的使用心法与国内镜像配置;第二,以 @ohos/axios 为代表的主流网络库集成与实战调用;第三,一个成熟 UI 工具库的用法解析;最后,手把手带你完成从零到上架一个自有 ohpm 包的完整流程。全文代码均基于 HarmonyOS NEXT API 12+,可直接拷贝到 DevEco Studio 项目中运行。

让我们开始吧。


一、ohpm 包管理器:从安装到镜像配置

1.1 为什么是 ohpm,而不是 npm?

在正式开始之前,有必要澄清一个常见的认知误区。npm(Node Package Manager)是 Node.js 生态的包管理工具,而 ohpm 脱胎于 OpenHarmony 生态,专为 ArkTS/ArkUI 语言设计。两者的核心差异不在于名字,而在于包的格式与索引地址。

npm 包以 CommonJS 或 ES Module 格式发布,通常后缀为 .js;ohpm 包则基于 HAR(Harmony Archive)格式分发,文件后缀为 .har。更重要的是,ohpm 的依赖解析和版本语义针对 OpenHarmony API 做了专门适配,直接用 npm 安装的包在鸿蒙项目中会出现大量兼容性问题。因此,在鸿蒙项目中,请务必使用 ohpm

1.2 安装 ohpm CLI

ohpm 通常随 DevEco Studio 一同安装。如果你使用的是独立 CLI 环境,可以通过以下命令手动安装:

# 使用 npm 安装 ohpm CLI(仅此场景例外,安装工具本身用 npm)
npm install -g @ohos/ohpm-cli

# 验证安装
ohpm --version
# 期望输出类似:ohpm 3.4.2

安装完成后,可以通过 ohpm --help 查看所有可用命令。以下是日常开发中最常用的几个命令:

ohpm --help

# 常用命令速查
# ohpm install    安装 package.json 中的所有依赖
# ohpm add        添加单个依赖包
# ohpm remove     移除依赖包
# ohpm info       查看某个包的信息
# ohpm search     搜索 ohpm 仓库中的包
# ohpm login      登录 ohpm 账户(发布包时需要)
# ohpm publish    发布包到 ohpm 仓库
# ohpm update     更新依赖包到最新兼容版本

1.3 配置国内镜像:解决下载慢的痛点

如果你在国内开发,默认的 OpenHarmony 官方源访问速度可能不理想。ohpm 支持多镜像源配置,切换到华为或腾讯等国内镜像可以大幅提升下载速度。

查看当前镜像配置:

ohpm config get registry
# 默认值:https://ohpm.openharmony.cn/ohpm

切换为华为镜像(推荐国内开发者使用):

ohpm config set registry https://ohpm.huawei.com/ohpm

如果你偏好腾讯云镜像:

ohpm config set registry https://mirrors.cloud.tencent.com/ohpm

在团队协作中,建议将镜像配置写入项目根目录的 .ohpmrc 文件(如果不存在则新建),而不是依赖全局配置,这样项目克隆后其他成员无需重复配置:

# 在项目根目录新建 .ohpmrc 文件,内容如下
# .ohpmrc
registry=https://ohpm.huawei.com/ohpm

提示:修改 .ohpmrc 后,建议运行 ohpm install 重新解析依赖,确保缓存已刷新。

1.4 在 DevEco Studio 中使用 ohpm

在 DevEco Studio 中,依赖管理实际上是通过图形界面封装了 ohpm 命令。打开项目的 module.json5oh-package.json5 文件,可以看到已声明的依赖列表。点击编辑器右上角的" syns dependencies" 按钮,或者使用菜单"File → Sync and Refresh Project",DevEco 会自动调用 ohpm 安装依赖。

理解这一层封装关系很重要:当你手动编辑 oh-package.json5 后,必须触发同步操作,否则项目无法识别新增的依赖。

切换回命令行也很自然——在项目根目录(含有 oh-package.json5 的目录)执行 ohpm 命令即可,它们操作的是同一套依赖解析引擎。


二、网络库实战:以 @ohos/axios 为例

2.1 为什么要用 axios 而不是原生 http 模块?

HarmonyOS NEXT 提供了原生的 http 模块,可以直接发起网络请求,能力足够但用法相对底层。每次请求都需要手动设置请求方法、Header、请求体、超时时间,处理响应时也要手动解析 JSON。工程实践中,这样的重复劳动不仅容易出错,还会让业务代码变得臃肿。

@ohos/axios 是对原生 http 模块的成熟封装,它借鉴了前端 axios 的 API 设计理念,提供了请求/响应拦截器、自动 JSON 转换、取消请求、并发控制等能力。对已有前端开发经验的团队来说,学习成本几乎为零。

2.2 安装 @ohos/axios

在项目根目录执行以下命令,将 axios 添加为 entry 模块的依赖:

ohpm add @ohos/axios

安装完成后,oh-package.json5 中会自动新增一行:

{
  "dependencies": {
    "@ohos/axios": "^2.2.0"
  }
}

如果你希望安装到特定版本:

ohpm add @ohos/axios@2.2.0

2.3 基础 GET 请求:列表数据拉取

假设我们要从某个 RESTful 接口获取用户列表,以下是完整调用代码。代码基于 API 12+ 的 ArkTS 语法编写,在 entry 模块的 Ability 或 ArkUI Page 中均可使用。

// entry/src/main/ets/utils/HttpService.ets

import axios, { AxiosInstance, AxiosResponse } from '@ohos/axios'

// 定义接口返回的数据结构
interface User {
  id: number
  name: string
  email: string
  avatar?: string
}

interface ApiResponse<T> {
  code: number
  message: string
  data: T
}

class HttpService {
  private client: AxiosInstance

  constructor(baseURL: string) {
    this.client = axios.create({
      baseURL: baseURL,
      timeout: 10000, // 10秒超时
      headers: {
        'Content-Type': 'application/json',
        'Accept-Language': 'zh-CN'
      }
    })

    // 添加请求拦截器:自动附加 token
    this.client.interceptors.request.use(
      (config) => {
        // 从 AppStorage 获取登录态 token
        const token = AppStorage.get<string>('auth_token')
        if (token) {
          config.headers['Authorization'] = `Bearer ${token}`
        }
        return config
      },
      (error) => {
        return Promise.reject(error)
      }
    )

    // 添加响应拦截器:统一处理错误码
    this.client.interceptors.response.use(
      (response: AxiosResponse<ApiResponse<unknown>>) => {
        const res = response.data
        if (res.code !== 0) {
          console.error(`[HttpService] API错误: ${res.code} - ${res.message}`)
          return Promise.reject(new Error(res.message))
        }
        return response
      },
      (error) => {
        console.error(`[HttpService] 网络错误: ${error.message}`)
        return Promise.reject(error)
      }
    )
  }

  // 获取用户列表
  async getUsers(page: number = 1, pageSize: number = 20): Promise<User[]> {
    const response = await this.client.get<ApiResponse<User[]>>('/users', {
      params: {
        page: page,
        pageSize: pageSize
      }
    })
    return response.data.data as User[]
  }
}

// 导出单例
export const userService = new HttpService('https://api.example.com/v1')

在 ArkUI 页面中使用这个服务:

// entry/src/main/ets/pages/UserListPage.ets

import { userService } from '../utils/HttpService'
import { User } from '../utils/HttpService'

@Entry
@Component
struct UserListPage {
  @State users: User[] = []
  @State isLoading: boolean = false
  @State errorMsg: string = ''

  aboutToAppear() {
    this.loadUsers()
  }

  async loadUsers() {
    this.isLoading = true
    this.errorMsg = ''
    try {
      this.users = await userService.getUsers(1, 20)
    } catch (e) {
      this.errorMsg = (e as Error).message
    } finally {
      this.isLoading = false
    }
  }

  build() {
    Column() {
      if (this.isLoading) {
        LoadingProgress()
          .width(50)
          .height(50)
          .margin({ top: 100 })
      } else if (this.errorMsg) {
        Text(`加载失败: ${this.errorMsg}`)
          .fontSize(16)
          .fontColor('#FF4444')
          .padding(20)
        Button('重试')
          .onClick(() => this.loadUsers())
      } else {
        List() {
          ForEach(this.users, (user: User) => {
            ListItem() {
              Row() {
                Image(user.avatar ?? '')
                  .width(40)
                  .height(40)
                  .borderRadius(20)
                Column() {
                  Text(user.name).fontSize(16).fontWeight(FontWeight.Medium)
                  Text(user.email).fontSize(12).fontColor('#888888')
                }.margin({ left: 12 })
              }
              .padding(12)
              .width('100%')
            }
          })
        }
        .width('100%')
        .layoutWeight(1)
      }
    }
    .width('100%')
    .height('100%')
    .padding(16)
  }
}

上述代码展示了 axios 的几个核心能力:基础配置化实例创建、请求/响应拦截器的注册、统一的错误处理,以及与 ArkUI @State 响应式状态的无缝集成。可以看到,业务代码几乎不需要关心网络细节,只关注数据模型和处理逻辑。

2.4 POST 请求与文件上传

在实际项目中,POST 请求和文件上传是常见场景。axios 对 FormData 的支持使文件上传变得简洁:

// 继续在 HttpService.ets 中追加方法

  // 登录接口(POST)
  async login(username: string, password: string): Promise<string> {
    const response = await this.client.post<ApiResponse<{ token: string }>>(
      '/auth/login',
      {
        username: username,
        password: password
      }
    )
    const token = response.data.data?.token ?? ''
    // 写入 AppStorage 供全局使用
    AppStorage.setOrCreate('auth_token', token)
    return token
  }

  // 上传图片(FormData + 文件路径)
  async uploadAvatar(userId: number, filePath: string): Promise<string> {
    const formData = new FormData()
    formData.append('userId', String(userId))
    formData.append('file', filePath)

    const response = await this.client.post<ApiResponse<{ url: string }>>(
      '/upload/avatar',
      formData,
      {
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      }
    )
    return response.data.data?.url ?? ''
  }

2.5 并发请求与请求取消

在列表页快速切换筛选条件时,前一个请求的结果可能已经过时,此时需要取消正在进行的请求。axios 内置了 CancelToken 机制:

// 演示并发请求和取消逻辑
async searchUsers(keyword: string, signal?: AbortSignal): Promise<User[]> {
  try {
    const response = await this.client.get<User[]>('/users/search', {
      params: { q: keyword },
      // axios 支持传入 AbortSignal(API 12+ 兼容性写法)
      cancelToken: new axios.CancelToken((cancel) => {
        // 如果外部传入了 signal,可以联动取消
        if (signal) {
          signal.addEventListener('abort', () => cancel('请求已取消'))
        }
      })
    })
    return response.data
  } catch (e) {
    if (axios.isCancel(e)) {
      console.info('[HttpService] 请求被取消:', (e as axios.Cancel).message)
      return []
    }
    throw e
  }
}

至此,网络库的核心用法已经覆盖完毕。总结一下要点:使用 ohpm add 安装依赖而非 npm,创建配置化的 Axios 实例以统一管理 baseURL 和超时,通过拦截器处理认证和错误,最后在页面中以 async/await 风格调用即可。


三、UI 工具库实战:@ohos/hypium 与组件封装思想

3.1 认识 Hypium 及其定位

@ohos/hypium 是 OpenHarmony 官方提供的基础测试框架,虽然名字里有"测试",但它的能力远不止于此。Hypium 提供了丰富的 UI 组件扩展、动画工具函数和布局辅助工具,可以被视为一个轻量级的 UI 工具库。

此外,社区中还有 @ohos/tauri-ui@ohos/relaxed-ui 等成熟 UI 库,提供开箱即用的组件(如图标库、按钮组、卡片组件、骨架屏等)。我们以 Hypium 的工具函数为主线,演示如何在实战中封装可复用的 UI 组件。

3.2 安装 Hypium

ohpm add @ohos/hypium

安装后,主要用到的是 hypium 中的测试注解和工具函数。如果你只需要其 UI 扩展部分,也可以直接 import 具体模块而非全量导入。

3.3 封装一个通用骨架屏组件

骨架屏(Skeleton Screen)是现代应用中提升感知加载体验的标配。它在数据尚未返回时显示内容区域的结构占位,让用户知道"这里将要显示什么"。在 ArkUI 中,我们来封装一个可复用的骨架屏组件,并将其设计为可在多个模块中共享的形式。

// entry/src/main/ets/components/SkeletonView.ets

/**
 * 骨架屏组件 - 模拟内容加载占位
 * 支持配置行数、高度和圆角,适用于列表、卡片、详情页等多种场景
 */

@Component
export struct SkeletonView {
  // 行配置:每行可指定高度和宽度百分比
  @Prop rows: SkeletonRowConfig[] = [
    { height: 16, widthPercent: 0.6 },
    { height: 12, widthPercent: 0.9 },
    { height: 12, widthPercent: 0.75 }
  ]
  // 是否显示闪烁动画
  @Prop animated: boolean = true
  // 主色调(骨架区域颜色)
  @Prop baseColor: string = '#EEEEEE'
  // 高亮色(闪烁时亮起部分)
  @Prop highlightColor: string = '#F5F5F5'
  // 组件圆角
  @Prop borderRadius: number = 4

  build() {
    Column({ space: 8 }) {
      ForEach(this.rows, (row: SkeletonRowConfig) => {
        Row() {
          Blank()
        }
        .height(row.height)
        .borderRadius(this.borderRadius)
        .backgroundColor(this.baseColor)
        .width(row.widthPercent * 100 + '%')
        .alignSelf(ItemAlign.Start)
        .clip(true)
        .onAppear(() => {
          if (this.animated) {
            // 简单闪烁动画通过 opacity 交替实现
            animateToImmediately({
              duration: 1200,
              iterations: -1,
              curve: Curve.EaseInOut,
              autoReverse: true
            }, () => {
              // 动画目标在状态变化后由系统渲染
            })
          }
        })
      })
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }
}

// 行配置的数据结构
export interface SkeletonRowConfig {
  height: number       // 高度(vp)
  widthPercent: number // 宽度百分比(0~1)
}

在这里插入图片描述

在页面中使用这个骨架屏组件,只需要传入行配置即可:

// entry/src/main/ets/pages/ProductDetailPage.ets

import { SkeletonView, SkeletonRowConfig } from '../components/SkeletonView'

@Entry
@Component
struct ProductDetailPage {
  @State product: Product | null = null
  @State isLoading: boolean = true

  @Builder
  skeletonBuilder() {
    SkeletonView({
      rows: [
        { height: 200, widthPercent: 1.0 },  // 大图区
        { height: 20, widthPercent: 0.5 },  // 标题
        { height: 14, widthPercent: 0.8 },   // 描述
        { height: 14, widthPercent: 0.6 },   // 描述第二行
        { height: 40, widthPercent: 1.0 }    // 按钮区
      ] as SkeletonRowConfig[],
      animated: true,
      baseColor: '#E8E8E8',
      highlightColor: '#F8F8F8',
      borderRadius: 8
    })
  }

  aboutToAppear() {
    // 模拟异步加载
    setTimeout(() => {
      this.product = this.fetchProduct()
      this.isLoading = false
    }, 1500)
  }

  fetchProduct(): Product {
    return {
      id: 1,
      name: '华为 Mate 60 Pro',
      description: '旗舰级智能手机,搭载自研麒麟芯片',
      price: 6999,
      imageUrl: ''
    }
  }

  build() {
    Column() {
      if (this.isLoading) {
        this.skeletonBuilder()
      } else if (this.product) {
        Column({ space: 16 }) {
          // 商品图片
          Image(this.product.imageUrl)
            .width('100%')
            .height(200)
            .backgroundColor('#F0F0F0')
            .objectFit(ImageFit.Cover)

          // 商品信息
          Column({ space: 8 }) {
            Text(this.product.name)
              .fontSize(22)
              .fontWeight(FontWeight.Bold)
            Text(`¥${this.product.price}`)
              .fontSize(18)
              .fontColor('#E74C3C')
            Text(this.product.description)
              .fontSize(14)
              .fontColor('#666666')
          }
          .width('100%')
          .padding(16)
          .alignItems(HorizontalAlign.Start)
        }
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}

interface Product {
  id: number
  name: string
  description: string
  price: number
  imageUrl: string
}

在这里插入图片描述

这个例子很好地展示了"组件封装"的思想:我们将骨架屏的渲染逻辑、数据结构和动画行为封装在 SkeletonView 组件中,调用方只需要描述"骨架有几行、每行多高"即可。@Prop 装饰器确保了父组件的状态变化能正确传递到子组件,同时保持了组件的独立性。

3.4 封装动画工具函数

Hypium 还提供了一些实用的动画工具函数,可以帮助快速实现常见的过渡效果。以下是一个基于 API 12+ 动画接口封装的过渡动画工具类:

// entry/src/main/ets/utils/AnimationUtils.ets

import curves from '@ohos.curves'

/**
 * 通用动画工具类
 * 提供常用的过渡动画封装
 */
export class AnimationUtils {

  /**
   * 创建弹性出现动画的配置
   * @param duration 动画时长(毫秒),默认 500ms
   */
  static springAppear(duration: number = 500): AnimationAttribute {
    return {
      duration: duration,
      curve: Curve.Spring,
      delayLayout: 0
    }
  }

  /**
   * 弹性消失动画配置
   */
  static springDisappear(duration: number = 300): AnimationAttribute {
    return {
      duration: duration,
      curve: Curve.FastOutSlowIn,
      delayLayout: 0
    }
  }

  /**
   * 页面转场动画 - 从右侧滑入
   */
  static slideFromRight(): TransitionEffect {
    return TransitionEffect.OPACITY.animation({
      duration: 350,
      curve: Curve.EaseOut
    }).combine(TransitionEffect.translate({ x: 300, y: 0 }))
  }

  /**
   * 页面转场动画 - 从底部上滑
   */
  static slideFromBottom(): TransitionEffect {
    return TransitionEffect.OPACITY.animation({
      duration: 400,
      curve: Curve.EaseInOut
    }).combine(TransitionEffect.translate({ x: 0, y: 500 }))
  }

  /**
   * 列表项渐入动画(带索引延迟)
   * 适用于列表中每个 item 有错开的动画效果
   */
  static listItemFade(index: number, baseDelay: number = 50): AnimatorResult {
    // 配合 LazyForEach 使用时,index 即为数据索引
    return {
      duration: 300,
      delay: index * baseDelay,
      curve: Curve.EaseOut
    } as unknown as AnimatorResult
  }
}

在列表页中应用错开动画:

// 在列表项容器上应用
ListItem() {
  Text(item.title)
    .fontSize(16)
}
.transition(this.isLoading
  ? TransitionEffect.OPACITY
  : AnimationUtils.slideFromRight())

至此,UI 工具库的核心用法已通过组件封装和动画工具两条路径完整呈现。核心思路是:不要在每个页面中重复写样式和动画逻辑,而是抽象出可配置的组件和工具函数,遵循 DRY(Don’t Repeat Yourself)原则。


四、发布自有三方库:从工程结构到 ohpm 上架

4.1 为什么需要发布自己的库?

当你在多个项目中反复使用同一套组件或工具函数时,维护多份副本的弊端显而易见:修改一处需要同步所有副本,版本不一致的风险极高。将这些可复用代码打包为 ohpm 包并发布到私有仓库(或开源到官方仓库),可以实现"一处修改、全局生效"的效果。这是团队工程化成熟的标志之一。

4.2 工程结构设计

一个规范的三方库工程应该具备清晰的分层结构。以下是推荐的多模块工程布局:

my-harmony-lib/
├── library/                          # 库模块(可发布的 HAR 包)
│   ├── src/
│   │   └── main/
│   │       ├── ets/                  # ArkTS 源代码
│   │       │   ├── components/       # UI 组件
│   │       │   │   ├── SkeletonView.ets
│   │       │   │   └── CardView.ets
│   │       │   ├── utils/            # 工具函数
│   │       │   │   ├── AnimationUtils.ets
│   │       │   │   └── HttpService.ets
│   │       │   └── index.ets         # 统一导出入口
│   │       └── module.json5
│   └── oh-package.json5              # 库的包配置
├── entry/                            # 示例/测试应用
│   ├── src/
│   └── module.json5
├── oh-package.json5                  # 根级工程配置
└── hvigorfile.ts                     # 构建配置

这种结构的好处是 library 模块可以被独立构建为 .har 文件,而 entry 模块仅用于开发和调试。

4.3 配置库的 oh-package.json5

oh-package.json5 是 ohpm 包的核心描述文件,与 npm 的 package.json 功能类似,但针对 OpenHarmony 做了扩展。以下是 library 模块的配置文件:

{
  "name": "@yourorg/harmony-utils",
  "version": "1.0.3",
  "description": "一套通用的鸿蒙工具库,包含UI组件和常用工具函数",
  "main": "./src/main/ets/index.ets",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://gitee.com/yourorg/harmony-utils.git"
  },
  "keywords": [
    "harmonyos",
    "openharmony",
    "arkts",
    "ui-components",
    "utils"
  ],
  "author": {
    "name": "Your Name",
    "email": "your@email.com"
  },
  "dependencies": {
    "@ohos/axios": "^2.2.0"
  },
  "devDependencies": {},
  "overrides": {},
  "peerDependencies": {},
  "publishConfig": {
    "registry": "https://ohpm.openharmony.cn/ohpm"
  }
}

有几个关键字段需要特别注意:

  • name:包名必须以 @scope/name 的格式命名(scope 通常为组织名或个人命名空间),这样可以避免与官方包或其他同名包冲突。
  • version:遵循语义化版本(SemVer)规范,即 主版本.次版本.修订号。向后不兼容的改动递增主版本,新增功能但向后兼容递增次版本,修复缺陷递增修订号。
  • main:指定包的入口文件,ohpm 会根据这个路径解析 import 语句。
  • peerDependencies:声明此包运行时的同伴依赖(即调用方应自行安装的依赖)。在这个示例中,@ohos/axios 被放在了普通 dependencies 中,这意味着安装此库时会自动拉取 axios。但更规范的做法是将其移入 peerDependencies,因为 axios 作为网络库,通常由业务项目自行管理版本。

4.4 统一导出:index.ets 的设计

为了让调用方能以简洁的方式使用库的各个模块,建议在 index.ets 中统一导出所有公开 API:

// library/src/main/ets/index.ets

// 导出 UI 组件
export { SkeletonView, SkeletonRowConfig } from './components/SkeletonView'
export { CardView } from './components/CardView'

// 导出工具函数
export { AnimationUtils } from './utils/AnimationUtils'

// 如果需要导出 HttpService(按需,建议业务层自行封装)
// export { HttpService } from './utils/HttpService'

// 统一版本号,便于调用方在调试时查看
export const LIB_VERSION: string = '1.0.3'

调用方安装后,只需一行 import 即可使用所有导出:

import { SkeletonView, AnimationUtils, LIB_VERSION } from '@yourorg/harmony-utils'

4.5 本地调试:npm link 与相对路径

在正式发布前,需要在本地项目验证库的可用性。有两种常用方式。

第一种方式是相对路径引用(适合临时调试):

# 在 entry 模块的 oh-package.json5 中添加本地依赖
{
  "dependencies": {
    "@yourorg/harmony-utils": "file:../library"
  }
}
# 然后执行
ohpm install

第二种方式是使用 npm link 发布本地包到全局缓存,再在其他项目中引用:

# 在 library 目录执行
cd library
npm link

# 在需要调试的项目目录执行
npm link @yourorg/harmony-utils

不过需要留意,npm link 作用于 npm 生态,在 ohpm 中引用时建议优先使用 file: 相对路径方式,因为它能准确模拟真实的依赖解析行为。

4.6 注册账户并登录 ohpm

要发布包到 OpenHarmony 官方仓库(或组织私有仓库),首先需要注册账户。访问 ohpm 官网 完成注册和实名认证后,在命令行登录:

ohpm login
# 系统会提示输入用户名、密码和邮箱
# 登录成功后,凭证会保存在本地 ~/.ohpmrc 中

如果你在团队中使用私有仓库(如华为内部源),需要在登录时指定对应的 registry:

ohpm login --registry https://ohpm.huawei.com/ohpm

4.7 构建与发布

发布前,建议先在本地构建并运行示例项目,确保没有任何 TypeScript 编译错误和运行时异常。

构建 library 模块为 HAR 文件:

# 在 library 目录执行构建
cd library
# DevEco Studio 中通常通过 Build → Build HAP/HSP 自动构建
# 命令行可通过 hvigor 构建
./hvigorw -p module=library@library clean assemble

构建产物位于 library/build/outputs/default/library.har

最后,执行发布命令:

# 确认登录状态
ohpm whoami

# 发布到默认 registry
ohpm publish

# 如果有预览版本需要发布到测试频道
ohpm publish --tag beta

# 查看发布是否成功
ohpm info @yourorg/harmony-utils

发布成功后,你的包就会出现在 ohpm 搜索结果中,全世界的开发者都可以通过 ohpm add @yourorg/harmony-utils 来使用你的作品了。

4.8 持续维护:版本迭代流程

一个活跃维护的库才能持续创造价值。以下是推荐的版本迭代流程:

首先,在 CHANGELOG.md 中记录每个版本的变更内容,遵循"新增、修改、修复、移除"的四类分类法。其次,使用 Git Tag 标记每个正式版本,配合 CI/CD 流水线实现自动化发布。最后,关注调用方的 Issue 反馈,及时修复 bug 并发布 patch 版本。


五、实战小结:ohpm 生态的最佳实践

经过以上四个章节的深入探讨,我们来总结一下在鸿蒙项目中高效使用三方库的核心原则。

优先使用 ohpm 而非 npm。这一点再怎么强调都不为过。ohpm 是 OpenHarmony 生态的官方包管理器,它理解 HAR 格式、API 版本约束和 OpenHarmony 的模块系统。虽然 npm 命令看起来功能更丰富,但在鸿蒙项目中直接使用 npm 安装的包大概率无法正常运行。

镜像配置是开发体验的基础。国内开发者务必配置国内镜像,将 .ohpmrc 文件提交到代码仓库可以确保团队一致性。

网络库选型要有前瞻性。一旦在项目中确定了 Axios 的封装方式,后续所有的 HTTP 调用都应该走同一套拦截器和错误处理逻辑。切忌在业务代码中绕过封装直接调用原生 http 模块。

组件封装要趁早。当你在第二个页面中需要复用同一个 UI 模式时,就该考虑将其抽取为独立组件了。骨架屏组件的封装过程已经展示了这一思维方式的落地路径。

发布自有库是工程化的里程碑。不要等到代码成熟后才考虑发布,从第一个可复用组件开始就保持"发布意识",会让你对 API 设计、包结构和文档有更高的标准。

保持依赖最小化。每个三方依赖都是潜在的风险点——版本冲突、作者弃更、API 不兼容等问题随时可能出现。在引入依赖前,评估"自己实现 vs 引入依赖"的成本收益比,做一个有判断力的工程师。


Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐