鸿蒙(HarmonyOS NEXT)表单校验别再手撸正则了 —— 我写了个 ArkTS 版 zod

鸿蒙(HarmonyOS NEXT)表单校验别再手撸正则了 ------ 我写了个 ArkTS 版 zod

一个零依赖、纯 ArkTS 的声明式校验库,内置手机号/身份证/银行卡等中国本地化规则 ,还能跟 ArkUI 表单组件实时联动。ohpm i @hmkit/validator 即用。

先说痛点

在鸿蒙上写一个注册表单,你大概率写过这样的代码:

kotlin 复制代码
// 校验手机号
if (!phone) {
  this.phoneErr = '请输入手机号';
} else if (!/^1[3-9]\d{9}$/.test(phone)) {
  this.phoneErr = '手机号格式不正确';
} else {
  this.phoneErr = '';
}
​
// 校验身份证...校验位算法你还记得吗?
// 校验银行卡...Luhn 算法又得查一遍
// 校验年龄、邮箱、确认密码...一个表单 100 行 if 起步

问题很明显:

  • 重复 :每个字段一坨 if/else,十个字段十坨。
  • 易错:身份证最后一位校验码、银行卡 Luhn,自己撸十有八九写错。
  • 难维护:校验逻辑散落在 UI 里,改个规则要翻半天。

前端有 zod/yup,后端有各种 validator,鸿蒙生态却一直缺一个顺手的 。于是我把这件事做了:@hmkit/validator

Before / After

同样校验「手机号 + 身份证 + 年龄满 18」:

Before(手撸,~30 行)

typescript 复制代码
let errors: string[] = [];
if (!phone) errors.push('请输入手机号');
else if (!/^1[3-9]\d{9}$/.test(phone)) errors.push('手机号格式不正确');
​
if (!idCard) errors.push('请输入身份证');
else if (!isValidIdCard(idCard)) errors.push('身份证不正确'); // isValidIdCard 还得自己写校验位
​
if (age === undefined) errors.push('请输入年龄');
else if (!Number.isInteger(age)) errors.push('年龄须为整数');
else if (age < 18) errors.push('需年满 18 岁');
// ...

After(链式声明,一目了然)

css 复制代码
import { v } from '@hmkit/validator';
​
// 注意:ArkTS 严格模式要求 Record 字面量 key 加引号
const schema = v.object({
  'phone':  v.string().required('请输入手机号').phone(),
  'idCard': v.string().required().idCard(),
  'age':    v.number().required().integer().min(18, '需年满 18 岁'),
});
​
const result = schema.validate({ 'phone': phone, 'idCard': idCard, 'age': age });
// result.errors: [{ path: 'phone', message: '请输入正确的手机号' }, ...]

校验逻辑变成声明,错误自带字段路径,读起来像说话。

一分钟上手

bash 复制代码
ohpm install @hmkit/validator
lua 复制代码
import { v } from '@hmkit/validator';
​
// 单值
v.string().required().phone().validate('13800138000'); // { valid: true, errors: [] }
​
// 自定义错误信息
v.number().min(18, '需年满 18 岁').validate(16);
// { valid: false, errors: [{ path: '', message: '需年满 18 岁' }] }

核心能力

🇨🇳 中国本地化规则,开箱即用

这是它和「移植个 zod」最不一样的地方 ------ 国内高频场景一个库全搞定,而且该有的校验算法都做对了:

规则 说明
phone() 手机号
idCard() 18 位身份证(校验出生日期 + 校验位)
bankCard() 银行卡(Luhn 算法)
creditCode() 统一社会信用代码(GB32100 校验位)
plateNumber() 车牌(含新能源)
landline() postalCode() 固话 / 邮编
chineseName() qq() wechat() 中文姓名 / QQ / 微信号
vin() ipv4() url() email() 车架号 / IPv4 / 网址 / 邮箱

身份证不光算校验位,还会拒掉 1900-02-31 这种假日期;信用代码也补了 mod-31 校验位 ------ 这些细节才是「能用在生产」和「demo」的区别。

🧩 数组、深层嵌套、跨字段

css 复制代码
// 数组逐元素校验,错误路径带下标
v.array(v.string().phone()).validate(['13800138000', '123']);
// errors: [{ path: '1', message: '请输入正确的手机号' }]
​
// 对象 ↔ 数组任意层级嵌套,路径自动拼接成 items.0.name
v.object({
  'items': v.array(v.object({ 'name': v.string().required('请输入商品名') })),
}).validate({ 'items': [{ 'name': '' }] });
// errors: [{ path: 'items.0.name', message: '请输入商品名' }]
​
// 跨字段:确认密码一致
v.object({ 'pwd': v.string().required(), 'confirm': v.string().required() })
  .refine(o => o['pwd'] === o['confirm'], '两次密码不一致', 'confirm');

⏳ 异步校验(远程查重)

用户名/手机号是否已注册,直接接异步规则:

dart 复制代码
const schema = v.string().required().customAsync(
  async (name) => await api.isUsernameAvailable(name), // 返回 true 通过
  '用户名已被占用',
);
await schema.validateAsync('taken'); // { valid: false, errors: [...] }

🎚️ 条件 / 可选

ini 复制代码
// 企业(type=company)才必填税号
v.string().requiredWhen('type', t => t === 'company', '企业必须填写税号');

招牌:跟 ArkUI 表单实时联动

光有校验逻辑还不够,鸿蒙开发者最想要的是「输入时实时变红、提交时整体校验」。库里带了一个 FormValidator 控制器,跟 TextInput 一接就是完整体验:

typescript 复制代码
import { v, FormValidator } from '@hmkit/validator';

@Entry
@Component
struct RegisterForm {
  private validator: FormValidator = new FormValidator({
    'username': v.string().required('请输入用户名').min(3, '用户名至少3位'),
    'phone':    v.string().required().phone(),
  });

  @State username: string = '';
  @State errors: Record<string, string> = {};

  build() {
    Column() {
      // 关键:text 用 $$ 双向绑定,输入不丢焦点
      TextInput({ text: $$this.username, placeholder: '用户名' })
        .onChange((val: string) => {
          const msg = this.validator.validateField('username', val);
          this.errors = msg === null ? {} as Record<string, string>
                                     : { 'username': msg } as Record<string, string>;
        })
      if (this.errors['username'] !== undefined) {
        Text(this.errors['username']).fontColor('#E64340')
      }

      Button('提交').onClick(() => {
        const errs = this.validator.validateAll({ 'username': this.username });
        this.errors = errs; // 提交时整体校验
      })
    }
  }
}

输入时变红框、提交时整体校验,一套搞定。

为什么敢用在生产

  • 零依赖、纯 ArkTS,只想要校验逻辑的人不被迫拉任何 UI 依赖。
  • 190 项单元测试覆盖每个规则 + 边界 + 反例(假身份证日期、错校验位、空值、跨字段、异步...),全过。
  • 校验算法对照国标实现(身份证 GB11643、信用代码 GB32100、银行卡 Luhn)。
  • 同步 API 向后兼容,异步规则按需启用。

链接

如果你也在鸿蒙上写表单,不妨试试,少撸几行正则。觉得有用点个 star,有需求提 issue,我会持续迭代 🙌

相关推荐
qizayaoshuap10 分钟前
# [特殊字符] 手电筒 — 鸿蒙ArkTS设备功能调用与UI交互设计
ui·华为·交互·harmonyos
JaneConan16 分钟前
鸿蒙报错速查:arkts-no-any any 类型禁用,用了就炸,根因 + 真解法
后端·harmonyos
listening77730 分钟前
HarmonyOS 6.1 UX动效实战:从“生硬”到“灵动”的物理动画引擎
华为·harmonyos·ux
2301_768103492 小时前
HarmonyOS趣味相机实战第29篇:AudioRenderer合成快门声、并发门闩与资源释放
harmonyos·arkts·资源管理·音频开发·audiorenderer
listening7773 小时前
HarmonyOS 6.1 混沌工程实战:从“故障免疫”到“韧性架构”
华为·架构·harmonyos
特立独行的猫a3 小时前
Python三方库鸿蒙PC移植指南PPT
harmonyos·移植·鸿蒙pc·python三方库
程序员黑豆3 小时前
鸿蒙应用开发:ForEach 循环渲染用法详解
前端·harmonyos
国服第二切图仔3 小时前
01-three-layer-architecture
harmonyos
b130538100493 小时前
HarmonyOS应用开发实战:萌宠日记 - 环比增长指示器
harmonyos·鸿蒙
爱写代码的森3 小时前
鸿蒙三方库 | harmony-utils之LocationUtil位置获取与订阅详解
华为·harmonyos·鸿蒙·huawei