在鸿蒙应用开发中,注册、资料编辑、企业认证等页面,通常都需要处理表单校验。

最开始,可能只是判断必填和手机号格式。随着业务增加,还会出现密码确认、条件必填、用户名查重,以及编辑页面只校验部分字段等需求。

如果这些逻辑都写在页面事件里,规则、错误提示和异步状态就容易混在一起,后续修改也需要反复检查多个入口。

我一直在维护的开源组件 **`@hmkit/validator`**,就是希望把这些校验规则整理成可复用的 Schema,让页面更专注于输入、展示和业务交互。

这篇文章结合几个常见场景,介绍它的接入方式和使用边界。

## 一、@hmkit/validator 能做什么

`@hmkit/validator` 是使用 ArkTS 实现的声明式校验库,核心 HAR 没有第三方运行时依赖。

目前提供的能力包括:

- 字符串、数字、布尔、日期、对象和数组校验;
- 手机号、身份证、银行卡、车牌、统一社会信用代码等业务规则;
- 跨字段校验、条件必填、默认值和数据转换;
- 异步校验,以及 FormValidator 的防抖、状态管理、取消和超时;
- Schema 组合、递归结构、JSON Schema 子集转换等进阶能力。

规则可以独立用于数据校验,也可以通过 FormValidator 与 ArkUI 表单交互连接起来。

## 二、安装并校验一个注册表单

在需要使用的模块中安装依赖:

```bash
ohpm install @hmkit/validator
```

下文基础示例使用 1.3 系列已有的 API。后面的 SchemaFactory 示例需要包含该入口的 1.4.0 版本。

OHPM 的可安装版本以中心仓页面为准,也可以从仓库 Release 获取对应 HAR:

- [OHPM 包页面](https://ohpm.openharmony.cn/#/cn/detail/@hmkit%2Fvalidator)
- [GitHub Releases](https://github.com/lxshwyan/harmony-validator/releases)

假设注册页面需要校验用户名、手机号、密码和确认密码,可以把规则集中定义:

```typescript
import { v } from '@hmkit/validator';

const registerSchema = v.object({
  'username': v.string()
    .required('请输入用户名')
    .min(3),

  'phone': v.string()
    .required('请输入手机号')
    .phone(),

  'password': v.string()
    .required('请输入密码')
    .min(8),

  'confirmPassword': v.string()
    .required('请再次输入密码')
}).refine(
  (data: Record<string, Object>): boolean =>
    data['password'] === data['confirmPassword'],
  '两次输入的密码不一致',
  'confirmPassword'
);

const values: Record<string, Object> = {
  'username': 'Alice',
  'phone': '13800138000',
  'password': 'abc12345',
  'confirmPassword': 'abc12346'
};

const result = registerSchema.validate(values);

if (!result.valid) {
  for (const error of result.errors) {
    console.info(`${error.path}: ${error.message}`);
  }
}
```

这个例子中,两次密码不同,错误会关联到 `confirmPassword` 字段:

```text
confirmPassword: 两次输入的密码不一致
```

页面可以根据错误的 `path` 显示对应提示。规则也可以放到独立文件中,由不同页面或业务流程复用。

需要注意:**字符串默认允许留空,必须填写的字段应显式调用 `.required()`。**

## 三、处理条件必填和数据转换

### 1. 根据其他字段决定是否必填

有些字段是否必填,取决于用户选择。

例如,选择企业身份时需要填写税号,个人身份则允许留空:

```typescript
import { v } from '@hmkit/validator';

const identitySchema = v.object({
  'identity': v.string().required(),

  'taxId': v.string().requiredWhen(
    'identity',
    (value: Object | null | undefined): boolean =>
      value === 'company',
    '企业身份需要填写税号'
  )
});
```

这样,条件规则可以与字段定义放在一起维护。

这里的规则只决定税号是否必填,格式、长度和业务有效性仍应根据实际要求补充。

### 2. 通过 parse 取得转换结果

有时除了校验,还需要处理输入数据。例如,将邀请码统一转换成大写:

```typescript
import { v } from '@hmkit/validator';

const inviteCode = v.string()
  .required()
  .transform<string>(
    (value: string): string => value.toUpperCase()
  );

const result = inviteCode.parse('vip2026');

if (result.success) {
  console.info(result.value); // VIP2026
}
```

需要取得转换后的数据时,应读取 `parse()` 或 `parseAsync()` 的结果。

`validate()` 返回的是校验结果,调用之后不应把原始输入当作已经完成转换的数据继续使用。

## 四、异步查重需要同时管理请求和状态

用户名查重看起来只是调用一次接口,但输入过程中还可能发生:

- 前一次查询未结束,用户已经输入了新内容;
- 较慢的旧结果晚于新结果返回;
- 用户离开页面时,请求仍在进行;
- 校验超时后,底层请求仍占用资源。

库通过 `customAsync()` 接入异步规则,FormValidator 负责字段交互和校验状态。

下面先用定时器模拟查重,展示异步规则和取消通知的连接方式。这段代码没有访问真实服务:

```typescript
import { v } from '@hmkit/validator';
import { FormValidator, FormTrigger } from '@hmkit/validator/form';
import { ValidationOptions } from '@hmkit/validator/execution';

function mockCheckUsername(
  value: string,
  options?: ValidationOptions
): Promise<boolean> {
  return new Promise<boolean>((resolve): void => {
    let unsubscribe: (() => void) | undefined;

    const timer = setTimeout((): void => {
      unsubscribe?.();
      resolve(value !== 'taken');
    }, 300);

    unsubscribe = options?.cancellation?.onCancel((): void => {
      clearTimeout(timer);
      resolve(false);
    });
  });
}
```

然后将规则交给 FormValidator:

```typescript
async function runUsernameDemo(): Promise<void> {
  const form = new FormValidator({
    'username': v.string()
      .required('请输入用户名')
      .min(3)
      .customAsync(mockCheckUsername, '用户名已被占用')
  }, {
    triggers: [FormTrigger.CHANGE],
    debounceMs: 300,
    timeoutMs: 2000
  });

  try {
    const values: Record<string, Object> = {
      'username': 'Alice'
    };

    // 对应输入变化事件
    await form.onChange('username', 'Alice', values);

    // 对应提交事件,校验通过后才执行处理器
    const errors = await form.submit(
      values,
      async (_data: Record<string, Object>): Promise<void> => {
        console.info('表单校验通过');
      }
    );

    console.info(JSON.stringify(errors));
  } finally {
    form.dispose();
  }
}
```

在实际页面中,可以将不同事件连接到对应入口:

| 页面交互 | 对应方法 |
| --- | --- |
| 输入内容变化 | `onChange()` |
| 输入框失焦 | `onBlur()` |
| 提交表单 | `submit()` |
| 重置表单状态 | `reset()` |
| 销毁控制器 | `dispose()` |

控制器还提供 `touched`、`dirty`、`validating` 和 `submitting` 等状态,页面可以据此更新提示和按钮状态。

同步 `validate()` 不执行异步规则。包含查重等异步规则时,需要使用异步校验入口。

### 接入真实请求时的注意事项

**取消通知需要由网络适配器主动处理。**

接入真实 HTTP 请求时,应订阅取消通知,并调用底层网络 API 取消请求、释放资源。仅让校验提前返回,并不能证明请求已经停止传输。

页面也需要根据隐藏、退出和销毁等生命周期清理校验任务。已经 dispose 的控制器,应重新创建后再使用。

仓库提供了可参考的 RCP 请求适配器:

[查看 HttpValidation.ets](https://github.com/lxshwyan/harmony-validator/blob/v1.4.0/example/HttpValidation.ets)

这个适配器是 HarmonyOS 示例,实际项目可以按照同一取消协议接入自己的网络层。

## 五、复用 Schema 时,注意实例共享

把规则集中管理以后,很容易写出下面这样的代码:

```typescript
const nameRule = v.string().required();

const shortName = nameRule.max(8);
const longName = nameRule.max(32);
```

基础链式方法会修改当前实例,这里的变量仍然引用同一个规则对象,不能据此得到两份独立规则。

需要独立规则时,可以使用每次都返回新实例的工厂函数。

1.4.0 还提供了 `SchemaFactory`,用于组织构造和派生:

```typescript
import { liteV, StringSchema } from '@hmkit/validator/lite';
import { SchemaFactory } from '@hmkit/validator/reuse';

const names = new SchemaFactory<StringSchema>(
  (): StringSchema => liteV.string().required()
);

const shortNames = names.derive(
  (schema: StringSchema): StringSchema => schema.max(8)
);

const longNames = names.derive(
  (schema: StringSchema): StringSchema => schema.max(32)
);

shortNames.create().validate('123456789'); // 不通过
longNames.create().validate('123456789');  // 通过
```

每次 `create()` 都重新执行构造函数,再应用派生配置。

因此,可变的子 Schema 也需要放在构造函数内部创建。如果构造函数始终返回外部共享实例,或者引用外部已有的子规则,这些引用仍然会共享。

## 六、接入前需要了解的几个边界

实际选型和接入时,除了 API 是否够用,还需要了解默认行为。

### 对象额外字段

对象的未知字段默认保留。

需要拒绝额外字段时,可以使用 `.strict()`;需要在解析输出中删除额外字段时,可以使用 `.strip()`。

### 显式模型与规则一致性

`v.object<Model>()` 保留显式结果类型,Model 与字段规则之间的一致性仍需要维护。

仓库提供了模型和 Schema 的单一来源生成工具,用于减少重复定义,但它只覆盖文档列出的生成子集。

### JSON Schema 支持范围

JSON Schema 导入和导出有明确的支持范围。

使用导入能力时,可以结合严格模式和诊断接口,检查当前未支持的约束,避免忽略规则差异。

### 仓库工具与 HAR

模型生成器、JS/TS Standard Schema 桥接和网络示例通过源码仓库提供,不随 HAR 安装。

其中,JS/TS 标准桥接用于对应运行环境的互操作,不代表 ArkTS 自动获得 TypeScript 的类型推导能力。

## 七、验证情况与开源地址

1.4.0 的验证记录包括:

- 299 项库回归测试;
- API12、API23 两组目标下的主入口和按需入口消费编译;
- API23 模拟器中的重启、取消、恢复和页面退出重进;
- 真实 HTTP 场景中,两种入口共 12 个在途取消请求的服务端断连确认。

目前还没有真机验证结果。实际项目采用的 SDK、网络适配器和页面生命周期,仍需要在目标环境中完成接入验证。

详细记录可以查看:

[1.4.0 验证报告](https://github.com/lxshwyan/harmony-validator/blob/master/doc/VALIDATION-1.4.0.md)

如果你正在开发鸿蒙注册表单、资料编辑页或业务数据录入功能,可以先从一个字段或一个表单开始接入。

项目地址:

码云: https://gitee.com/lxsh_wyan/harmony-validator
  https://github.com/lxshwyan/harmony-validator 
https://ohpm.openharmony.cn/#/cn/detail/@hmkit%2Fvalidator

欢迎反馈实际使用中的问题。提交 Issue 时,附上 SDK 版本、最小示例、输入数据和预期结果,会更方便复现和修复。

后续我也会根据具体业务需求,继续完善规则复用、文档和工程接入体验。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐