HarmonyOS 表单验证机制深度解析与实践

在HarmonyOS应用开发中,表单验证是保障数据质量和用户体验的关键环节。一个健壮的表单验证系统不仅能够有效防止无效数据进入后端系统,还能为用户提供即时、友好的反馈,提升整体交互体验。本文将深入探讨表单验证的核心原理、实现策略和最佳实践。


一、表单验证的重要性与挑战

1.1 表单验证的意义

表单验证在应用开发中具有多重意义:

  • 数据完整性:确保必填字段不缺失
  • 数据正确性:验证数据格式符合预期
  • 安全性:防止恶意输入和注入攻击
  • 用户体验:提供即时反馈,减少错误提交
  • 后端保护:减轻服务器端验证压力

1.2 表单验证的挑战

在实际开发中,表单验证面临诸多挑战:

  • 多平台适配:不同设备和屏幕尺寸的适配
  • 实时验证性能:高频输入场景下的性能优化
  • 复杂业务规则:多字段联动验证逻辑
  • 国际化支持:不同地区的格式差异(如手机号、身份证等)
  • 可维护性:验证规则的灵活扩展和管理

二、验证规则体系设计

2.1 验证规则分类

根据验证的复杂度和用途,可将验证规则分为以下几类:

类别 说明 示例
格式验证 验证数据格式是否符合规范 邮箱、手机号、URL
长度验证 验证字符串长度范围 密码长度、用户名长度
数值验证 验证数值范围 年龄、金额、数量
逻辑验证 验证业务逻辑规则 密码确认、日期范围
自定义验证 特定业务场景的验证 身份证校验、企业税号

2.2 验证结果接口设计

typescript 复制代码
export interface ValidationResult {
  valid: boolean;
  message: string;
}

该接口定义了验证结果的基本结构:

  • valid:布尔值,表示验证是否通过
  • message:字符串,包含验证失败时的错误提示信息

三、核心验证规则实现

3.1 邮箱验证

3.1.1 正则表达式设计
typescript 复制代码
export function isEmail(value: string): boolean {
  const regex: RegExp = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(value);
}

正则表达式解析

  • ^[^\s@]+:匹配邮箱用户名部分,不包含空格和@符号
  • @:匹配@符号
  • [^\s@]+:匹配域名部分
  • \.[^\s@]+$:匹配顶级域名
3.1.2 完整验证函数
typescript 复制代码
export function validateEmail(value: string): ValidationResult {
  if (!value) {
    return { valid: false, message: '请输入邮箱地址' };
  }
  if (!isEmail(value)) {
    return { valid: false, message: '请输入有效的邮箱地址,如: example@email.com' };
  }
  return { valid: true, message: '✓ 邮箱格式正确' };
}

验证逻辑

  1. 检查是否为空
  2. 检查格式是否符合邮箱规范
  3. 返回相应的验证结果

3.2 手机号验证

3.2.1 正则表达式设计
typescript 复制代码
export function isPhone(value: string): boolean {
  const regex: RegExp = /^1[3-9]\d{9}$/;
  return regex.test(value);
}

正则表达式解析

  • ^1:以1开头(中国大陆手机号特征)
  • [3-9]:第二位为3-9(排除1和2)
  • \d{9}$:后面跟着9位数字
3.2.2 完整验证函数
typescript 复制代码
export function validatePhone(value: string): ValidationResult {
  if (!value) {
    return { valid: false, message: '请输入手机号码' };
  }
  if (!isPhone(value)) {
    return { valid: false, message: '请输入有效的11位手机号码' };
  }
  return { valid: true, message: '✓ 手机号码格式正确' };
}

3.3 身份证验证

3.3.1 正则表达式设计
typescript 复制代码
export function isIdCard(value: string): boolean {
  const regex: RegExp = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
  return regex.test(value);
}

正则表达式解析

  • ^[1-9]\d{5}:6位行政区划代码(第一位不为0)
  • (18|19|20)\d{2}:年份(1800-2099)
  • (0[1-9]|1[0-2]):月份(01-12)
  • (0[1-9]|[12]\d|3[01]):日期(01-31)
  • \d{3}:顺序码
  • [\dXx]$:校验码(数字或X)
3.3.2 完整验证函数
typescript 复制代码
export function validateIdCard(value: string): ValidationResult {
  if (!value) {
    return { valid: false, message: '请输入身份证号码' };
  }
  if (!isIdCard(value)) {
    return { valid: false, message: '请输入有效的18位身份证号码' };
  }
  return { valid: true, message: '✓ 身份证号码格式正确' };
}

3.4 密码强度验证

3.4.1 密码强度检测
typescript 复制代码
export function isPasswordStrong(value: string): boolean {
  if (value.length < 8) {
    return false;
  }
  const hasUpper = /[A-Z]/.test(value);
  const hasLower = /[a-z]/.test(value);
  const hasNumber = /\d/.test(value);
  const hasSpecial = /[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/.test(value);
  return hasUpper && hasLower && (hasNumber || hasSpecial);
}

密码强度规则

  1. 长度至少8位
  2. 包含大写字母
  3. 包含小写字母
  4. 包含数字或特殊字符
3.4.2 密码强度等级评估
typescript 复制代码
export interface PasswordStrength {
  level: number;
  label: string;
  color: string;
}

export function getPasswordStrength(value: string): PasswordStrength {
  let level = 0;
  
  if (value.length >= 6) level++;
  if (value.length >= 10) level++;
  if (/[a-z]/.test(value)) level++;
  if (/[A-Z]/.test(value)) level++;
  if (/\d/.test(value)) level++;
  if (/[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/.test(value)) level++;
  
  const result: PasswordStrength = { level: level, label: '', color: '' };
  
  if (level <= 2) {
    result.label = '弱';
    result.color = '#FF4757';
  } else if (level <= 4) {
    result.label = '中';
    result.color = '#FFA502';
  } else {
    result.label = '强';
    result.color = '#2ED573';
  }
  
  return result;
}

强度评估标准

等级 分数范围 标签 颜色
0-2 红色
3-4 橙色
5-6 绿色

3.5 长度范围验证

typescript 复制代码
export function validateLengthRange(value: string, min: number, max: number): ValidationResult {
  if (!value) {
    return { valid: false, message: '请输入内容' };
  }
  if (value.length < min) {
    return { valid: false, message: `内容长度不能少于${min}个字符` };
  }
  if (value.length > max) {
    return { valid: false, message: `内容长度不能超过${max}个字符` };
  }
  return { valid: true, message: '✓ 长度符合要求' };
}

3.6 数字验证

typescript 复制代码
export function isNumber(value: string): boolean {
  const regex: RegExp = /^-?\d+(\.\d+)?$/;
  return regex.test(value);
}

export function validateNumber(value: string): ValidationResult {
  if (!value) {
    return { valid: false, message: '请输入数字' };
  }
  if (!isNumber(value)) {
    return { valid: false, message: '请输入有效的数字' };
  }
  return { valid: true, message: '✓ 数字格式正确' };
}

四、验证策略模式

4.1 策略模式概述

策略模式是一种行为设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以互相替换。在表单验证中,策略模式可以帮助我们:

  • 将验证逻辑与UI组件解耦
  • 实现验证规则的动态切换
  • 提高代码的可维护性和扩展性

4.2 验证策略接口设计

typescript 复制代码
export interface ValidatorStrategy {
  validate(value: string): ValidationResult;
}

4.3 具体策略实现

typescript 复制代码
export class EmailValidator implements ValidatorStrategy {
  validate(value: string): ValidationResult {
    if (!value) {
      return { valid: false, message: '请输入邮箱地址' };
    }
    const regex: RegExp = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!regex.test(value)) {
      return { valid: false, message: '请输入有效的邮箱地址' };
    }
    return { valid: true, message: '✓ 邮箱格式正确' };
  }
}

export class PhoneValidator implements ValidatorStrategy {
  validate(value: string): ValidationResult {
    if (!value) {
      return { valid: false, message: '请输入手机号码' };
    }
    const regex: RegExp = /^1[3-9]\d{9}$/;
    if (!regex.test(value)) {
      return { valid: false, message: '请输入有效的11位手机号码' };
    }
    return { valid: true, message: '✓ 手机号码格式正确' };
  }
}

export class RequiredValidator implements ValidatorStrategy {
  validate(value: string): ValidationResult {
    if (!value || value.trim().length === 0) {
      return { valid: false, message: '此字段为必填项' };
    }
    return { valid: true, message: '' };
  }
}

export class LengthValidator implements ValidatorStrategy {
  private min: number;
  private max: number;
  
  constructor(min: number, max: number) {
    this.min = min;
    this.max = max;
  }
  
  validate(value: string): ValidationResult {
    if (!value) {
      return { valid: false, message: '请输入内容' };
    }
    if (value.length < this.min) {
      return { valid: false, message: `内容长度不能少于${this.min}个字符` };
    }
    if (value.length > this.max) {
      return { valid: false, message: `内容长度不能超过${this.max}个字符` };
    }
    return { valid: true, message: '✓ 长度符合要求' };
  }
}

4.4 策略上下文

typescript 复制代码
export class ValidationContext {
  private strategies: ValidatorStrategy[] = [];
  
  addStrategy(strategy: ValidatorStrategy): void {
    this.strategies.push(strategy);
  }
  
  validate(value: string): ValidationResult {
    for (let i = 0; i < this.strategies.length; i++) {
      const result = this.strategies[i].validate(value);
      if (!result.valid) {
        return result;
      }
    }
    return { valid: true, message: '✓ 验证通过' };
  }
}

4.5 策略模式使用示例

typescript 复制代码
// 创建验证上下文
const emailContext = new ValidationContext();
emailContext.addStrategy(new RequiredValidator());
emailContext.addStrategy(new EmailValidator());

// 执行验证
const result = emailContext.validate('test@example.com');
console.log(result); // { valid: true, message: '✓ 验证通过' }

五、表单验证组件设计

5.1 验证输入组件

typescript 复制代码
@ComponentV2
struct ValidatedInput {
  @Param label: string = '';
  @Param placeholder: string = '';
  @Param strategies: ValidatorStrategy[] = [];
  @Event onChange: (value: string, result: ValidationResult) => void = () => {};
  
  @Local value: string = '';
  @Local result: ValidationResult = { valid: false, message: '' };
  
  build() {
    Column({ space: 8 }) {
      Text(this.label)
        .fontSize(14)
        .fontColor('#1C1C1E')
      
      TextInput({ placeholder: this.placeholder, text: this.value })
        .width('100%')
        .height(44)
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
        .padding({ left: 12, right: 12 })
        .onChange((value: string) => {
          this.value = value;
          this.validate(value);
        })
      
      if (this.value) {
        Text(this.result.message)
          .fontSize(12)
          .fontColor(this.result.valid ? '#2ED573' : '#FF4757')
      }
    }
  }
  
  private validate(value: string): void {
    for (let i = 0; i < this.strategies.length; i++) {
      const strategyResult = this.strategies[i].validate(value);
      if (!strategyResult.valid) {
        this.result = strategyResult;
        this.onChange(value, strategyResult);
        return;
      }
    }
    this.result = { valid: true, message: '✓ 验证通过' };
    this.onChange(value, this.result);
  }
}

5.2 表单验证管理器

typescript 复制代码
export class FormValidator {
  private fields: Map<string, ValidationResult> = new Map();
  
  setFieldResult(fieldName: string, result: ValidationResult): void {
    this.fields.set(fieldName, result);
  }
  
  getFieldResult(fieldName: string): ValidationResult | undefined {
    return this.fields.get(fieldName);
  }
  
  isFormValid(): boolean {
    for (const [, result] of this.fields) {
      if (!result.valid) {
        return false;
      }
    }
    return this.fields.size > 0;
  }
  
  reset(): void {
    this.fields.clear();
  }
}

六、实战案例:用户注册表单

6.1 表单数据模型

typescript 复制代码
interface UserRegistration {
  email: string;
  phone: string;
  password: string;
  nickname: string;
  age?: string;
}

6.2 表单验证实现

typescript 复制代码
@Entry
@ComponentV2
struct RegistrationForm {
  @Local formData: UserRegistration = {
    email: '',
    phone: '',
    password: '',
    nickname: '',
    age: ''
  };
  
  @Local emailResult: ValidationResult = { valid: false, message: '' };
  @Local phoneResult: ValidationResult = { valid: false, message: '' };
  @Local passwordResult: ValidationResult = { valid: false, message: '' };
  @Local nicknameResult: ValidationResult = { valid: false, message: '' };
  @Local ageResult: ValidationResult = { valid: true, message: '' };
  
  @Local passwordStrength: PasswordStrength = { level: 0, label: '', color: '' };
  @Local allValid: boolean = false;
  
  build() {
    Column({ space: 0 }) {
      this.buildHeader();
      Scroll() {
        Column({ space: 20 }) {
          this.buildEmailField();
          this.buildPhoneField();
          this.buildPasswordField();
          this.buildNicknameField();
          this.buildAgeField();
          this.buildSubmitButton();
        }
        .width('100%')
        .padding(20)
      }
      .width('100%')
      .height('100%')
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F7')
  }
  
  @Builder
  buildHeader() {
    Row() {
      Text('用户注册')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1C1C1E')
    }
    .width('100%')
    .height(56)
    .padding({ left: 20 })
    .alignItems(VerticalAlign.Center)
    .backgroundColor('#FFFFFF')
  }
  
  @Builder
  buildEmailField() {
    Column({ space: 8 }) {
      Row({ space: 4 }) {
        Text('邮箱')
          .fontSize(14)
          .fontColor('#1C1C1E')
        Text('*')
          .fontSize(14)
          .fontColor('#FF4757')
      }
      
      TextInput({ placeholder: '请输入邮箱地址', text: this.formData.email })
        .width('100%')
        .height(44)
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
        .padding({ left: 12, right: 12 })
        .onChange((value: string) => {
          this.formData.email = value;
          this.emailResult = validateEmail(value);
          this.checkAllValid();
        })
      
      if (this.formData.email) {
        Text(this.emailResult.message)
          .fontSize(12)
          .fontColor(this.emailResult.valid ? '#2ED573' : '#FF4757')
      }
    }
  }
  
  @Builder
  buildPhoneField() {
    Column({ space: 8 }) {
      Row({ space: 4 }) {
        Text('手机号')
          .fontSize(14)
          .fontColor('#1C1C1E')
        Text('*')
          .fontSize(14)
          .fontColor('#FF4757')
      }
      
      TextInput({ placeholder: '请输入手机号码', text: this.formData.phone })
        .width('100%')
        .height(44)
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
        .padding({ left: 12, right: 12 })
        .type(InputType.Number)
        .onChange((value: string) => {
          this.formData.phone = value;
          this.phoneResult = validatePhone(value);
          this.checkAllValid();
        })
      
      if (this.formData.phone) {
        Text(this.phoneResult.message)
          .fontSize(12)
          .fontColor(this.phoneResult.valid ? '#2ED573' : '#FF4757')
      }
    }
  }
  
  @Builder
  buildPasswordField() {
    Column({ space: 8 }) {
      Row({ space: 4 }) {
        Text('密码')
          .fontSize(14)
          .fontColor('#1C1C1E')
        Text('*')
          .fontSize(14)
          .fontColor('#FF4757')
      }
      
      TextInput({ placeholder: '请输入密码(至少8位)', text: this.formData.password })
        .width('100%')
        .height(44)
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
        .padding({ left: 12, right: 12 })
        .type(InputType.Password)
        .onChange((value: string) => {
          this.formData.password = value;
          this.passwordResult = validatePassword(value);
          this.passwordStrength = getPasswordStrength(value);
          this.checkAllValid();
        })
      
      Row({ space: 8 }) {
        if (this.formData.password) {
          Text(this.passwordResult.message)
            .fontSize(12)
            .fontColor(this.passwordResult.valid ? '#2ED573' : '#FF4757')
          
          if (this.passwordStrength.label) {
            Text(`强度: ${this.passwordStrength.label}`)
              .fontSize(12)
              .fontColor(this.passwordStrength.color)
          }
        }
      }
      
      if (this.formData.password) {
        Row({ space: 2 }) {
          ForEach([1, 2, 3, 4, 5, 6], (index: number) => {
            Row()
              .width(24)
              .height(6)
              .borderRadius(3)
              .backgroundColor(index <= this.passwordStrength.level ? this.passwordStrength.color : '#E0E0E0')
          })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
      }
    }
  }
  
  @Builder
  buildNicknameField() {
    Column({ space: 8 }) {
      Row({ space: 4 }) {
        Text('昵称')
          .fontSize(14)
          .fontColor('#1C1C1E')
        Text('*')
          .fontSize(14)
          .fontColor('#FF4757')
      }
      
      TextInput({ placeholder: '请输入昵称(2-10个字符)', text: this.formData.nickname })
        .width('100%')
        .height(44)
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
        .padding({ left: 12, right: 12 })
        .onChange((value: string) => {
          this.formData.nickname = value;
          this.nicknameResult = validateLengthRange(value, 2, 10);
          this.checkAllValid();
        })
      
      if (this.formData.nickname) {
        Text(this.nicknameResult.message)
          .fontSize(12)
          .fontColor(this.nicknameResult.valid ? '#2ED573' : '#FF4757')
      }
    }
  }
  
  @Builder
  buildAgeField() {
    Column({ space: 8 }) {
      Text('年龄')
        .fontSize(14)
        .fontColor('#1C1C1E')
      
      TextInput({ placeholder: '请输入年龄(选填)', text: this.formData.age || '' })
        .width('100%')
        .height(44)
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
        .padding({ left: 12, right: 12 })
        .type(InputType.Number)
        .onChange((value: string) => {
          this.formData.age = value;
          if (value) {
            this.ageResult = validateNumber(value);
          } else {
            this.ageResult = { valid: true, message: '' };
          }
          this.checkAllValid();
        })
      
      if (this.formData.age) {
        Text(this.ageResult.message)
          .fontSize(12)
          .fontColor(this.ageResult.valid ? '#2ED573' : '#FF4757')
      }
    }
  }
  
  @Builder
  buildSubmitButton() {
    Button('注册')
      .width('100%')
      .height(48)
      .backgroundColor(this.allValid ? '#007DFF' : '#CCCCCC')
      .fontColor('#FFFFFF')
      .borderRadius(8)
      .enabled(this.allValid)
      .onClick(() => {
        if (this.allValid) {
          this.submitForm();
        }
      })
    
    Text(this.allValid ? '✓ 所有字段验证通过' : '请完善表单信息')
      .fontSize(12)
      .fontColor(this.allValid ? '#2ED573' : '#98989A')
      .width('100%')
      .textAlign(TextAlign.Center)
  }
  
  private checkAllValid(): void {
    this.allValid = 
      this.emailResult.valid &&
      this.phoneResult.valid &&
      this.passwordResult.valid &&
      this.nicknameResult.valid &&
      (this.formData.age ? this.ageResult.valid : true);
  }
  
  private submitForm(): void {
    console.log('表单提交:', this.formData);
    // 调用API提交表单
  }
}

七、高级验证技术

7.1 异步验证

在某些场景下,验证需要调用后端API(如检查用户名是否已存在):

typescript 复制代码
export interface AsyncValidationResult {
  valid: boolean;
  message: string;
  loading: boolean;
}

export async function validateUsername(username: string): Promise<ValidationResult> {
  if (!username) {
    return { valid: false, message: '请输入用户名' };
  }
  
  // 模拟API调用
  await new Promise(resolve => setTimeout(resolve, 500));
  
  // 模拟检查结果
  const exists = Math.random() > 0.7;
  
  if (exists) {
    return { valid: false, message: '该用户名已被注册' };
  }
  
  return { valid: true, message: '✓ 用户名可用' };
}

7.2 多字段联动验证

某些验证规则需要多个字段配合:

typescript 复制代码
export function validatePasswordMatch(password: string, confirmPassword: string): ValidationResult {
  if (!confirmPassword) {
    return { valid: false, message: '请再次输入密码' };
  }
  if (password !== confirmPassword) {
    return { valid: false, message: '两次输入的密码不一致' };
  }
  return { valid: true, message: '✓ 密码一致' };
}

export function validateDateRange(startDate: string, endDate: string): ValidationResult {
  if (!startDate) {
    return { valid: false, message: '请选择开始日期' };
  }
  if (!endDate) {
    return { valid: false, message: '请选择结束日期' };
  }
  
  const start = new Date(startDate);
  const end = new Date(endDate);
  
  if (end < start) {
    return { valid: false, message: '结束日期不能早于开始日期' };
  }
  
  return { valid: true, message: '✓ 日期范围有效' };
}

7.3 自定义验证规则

允许用户扩展验证规则:

typescript 复制代码
export interface CustomRule {
  name: string;
  regex: RegExp;
  errorMessage: string;
}

export function validateWithCustomRule(value: string, rule: CustomRule): ValidationResult {
  if (!value) {
    return { valid: false, message: '请输入内容' };
  }
  if (!rule.regex.test(value)) {
    return { valid: false, message: rule.errorMessage };
  }
  return { valid: true, message: '✓ 验证通过' };
}

八、性能优化策略

8.1 防抖优化

对于实时验证场景,使用防抖减少验证次数:

typescript 复制代码
type SimpleFunction = () => void;

export function debounce(fn: SimpleFunction, delay: number): SimpleFunction {
  let timer: number | null = null;
  return (): void => {
    if (timer !== null) {
      clearTimeout(timer);
    }
    const timerValue = setTimeout(() => {
      fn();
    }, delay);
    timer = timerValue as number;
  };
}

使用示例

typescript 复制代码
@Local debouncedValidate: () => void = debounce(() => {
  this.validateField();
}, 300);

onInputChange(value: string) {
  this.value = value;
  this.debouncedValidate();
}

8.2 验证缓存

对于复杂验证规则,缓存验证结果:

typescript 复制代码
export class ValidationCache {
  private cache: Map<string, ValidationResult> = new Map();
  
  get(key: string): ValidationResult | undefined {
    return this.cache.get(key);
  }
  
  set(key: string, result: ValidationResult): void {
    this.cache.set(key, result);
  }
  
  clear(): void {
    this.cache.clear();
  }
  
  validate(key: string, value: string, validator: (value: string) => ValidationResult): ValidationResult {
    const cached = this.cache.get(key);
    if (cached) {
      return cached;
    }
    
    const result = validator(value);
    this.cache.set(key, result);
    return result;
  }
}

8.3 批量验证

对于包含大量字段的表单,支持批量验证:

typescript 复制代码
export function validateForm(fields: Record<string, string>, validators: Record<string, (value: string) => ValidationResult>): Record<string, ValidationResult> {
  const results: Record<string, ValidationResult> = {};
  
  for (const [fieldName, value] of Object.entries(fields)) {
    const validator = validators[fieldName];
    if (validator) {
      results[fieldName] = validator(value);
    }
  }
  
  return results;
}

九、验证错误处理与用户反馈

9.1 错误提示样式

typescript 复制代码
@Builder
buildValidationMessage(result: ValidationResult) {
  Row() {
    if (result.valid) {
      Image($r('app.media.check_icon'))
        .width(16)
        .height(16)
      Text(result.message)
        .fontSize(12)
        .fontColor('#2ED573')
    } else {
      Image($r('app.media.error_icon'))
        .width(16)
        .height(16)
      Text(result.message)
        .fontSize(12)
        .fontColor('#FF4757')
    }
  }
  .alignItems(VerticalAlign.Center)
  .space(4)
}

9.2 表单错误汇总

typescript 复制代码
@Builder
buildErrorSummary(errors: string[]) {
  if (errors.length > 0) {
    Column({ space: 4 }) {
      Text('请修正以下错误:')
        .fontSize(14)
        .fontColor('#FF4757')
        .fontWeight(FontWeight.Bold)
      
      ForEach(errors, (error: string) => {
        Row({ space: 4 }) {
          Text('•')
            .fontSize(12)
            .fontColor('#FF4757')
          Text(error)
            .fontSize(12)
            .fontColor('#FF4757')
        }
      })
    }
    .padding(12)
    .backgroundColor('#FFF0F0')
    .borderRadius(8)
  }
}

十、测试与质量保障

10.1 单元测试

typescript 复制代码
import { describe, it, expect } from '@ohos/hypium';

describe('Validator Tests', () => {
  it('Email validation', () => {
    expect(isEmail('test@example.com')).toBe(true);
    expect(isEmail('invalid-email')).toBe(false);
    expect(isEmail('')).toBe(false);
  });
  
  it('Phone validation', () => {
    expect(isPhone('13812345678')).toBe(true);
    expect(isPhone('12345678901')).toBe(false);
    expect(isPhone('1381234567')).toBe(false);
  });
  
  it('Password strength', () => {
    expect(isPasswordStrong('Weak123')).toBe(false);
    expect(isPasswordStrong('Strong@123')).toBe(true);
    expect(isPasswordStrong('Short')).toBe(false);
  });
  
  it('ID card validation', () => {
    expect(isIdCard('110101199003074512')).toBe(true);
    expect(isIdCard('11010119900307451X')).toBe(true);
    expect(isIdCard('123456789012345678')).toBe(false);
  });
});

10.2 边界条件测试

测试场景 输入 预期结果
空字符串 '' 验证失败
空格字符串 ' ' 验证失败
最小长度 'ab' (最小2位) 验证通过
最大长度 'abcdefghij' (最大10位) 验证通过
超出范围 'abcdefghijk' (最大10位) 验证失败
特殊字符 'test@#$%' 根据规则判断

十一、总结

本文深入探讨了HarmonyOS应用中的表单验证机制,涵盖以下核心内容:

  1. 验证规则设计:详细介绍了邮箱、手机号、身份证、密码等常用验证规则的正则表达式设计
  2. 策略模式应用:通过策略模式实现验证规则的灵活组合和扩展
  3. 组件化设计:构建可复用的验证输入组件和表单验证管理器
  4. 实战案例:完整的用户注册表单实现,展示验证机制的实际应用
  5. 高级技术:异步验证、多字段联动、自定义规则等高级验证技术
  6. 性能优化:防抖、缓存、批量验证等性能优化策略

通过本文的学习,您可以:

  • 理解表单验证的核心原理和设计模式
  • 掌握常用验证规则的实现方法
  • 构建健壮、可扩展的表单验证系统
  • 提升用户体验和数据质量

附录:验证器工具函数完整代码

typescript 复制代码
export interface ValidationResult {
  valid: boolean;
  message: string;
}

export interface PasswordStrength {
  level: number;
  label: string;
  color: string;
}

export function isEmail(value: string): boolean {
  const regex: RegExp = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(value);
}

export function isPhone(value: string): boolean {
  const regex: RegExp = /^1[3-9]\d{9}$/;
  return regex.test(value);
}

export function isIdCard(value: string): boolean {
  const regex: RegExp = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
  return regex.test(value);
}

export function isPasswordStrong(value: string): boolean {
  if (value.length < 8) {
    return false;
  }
  const hasUpper = /[A-Z]/.test(value);
  const hasLower = /[a-z]/.test(value);
  const hasNumber = /\d/.test(value);
  const hasSpecial = /[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/.test(value);
  return hasUpper && hasLower && (hasNumber || hasSpecial);
}

export function isNumber(value: string): boolean {
  const regex: RegExp = /^-?\d+(\.\d+)?$/;
  return regex.test(value);
}

export function validateEmail(value: string): ValidationResult {
  if (!value) {
    return { valid: false, message: '请输入邮箱地址' };
  }
  if (!isEmail(value)) {
    return { valid: false, message: '请输入有效的邮箱地址,如: example@email.com' };
  }
  return { valid: true, message: '✓ 邮箱格式正确' };
}

export function validatePhone(value: string): ValidationResult {
  if (!value) {
    return { valid: false, message: '请输入手机号码' };
  }
  if (!isPhone(value)) {
    return { valid: false, message: '请输入有效的11位手机号码' };
  }
  return { valid: true, message: '✓ 手机号码格式正确' };
}

export function validateIdCard(value: string): ValidationResult {
  if (!value) {
    return { valid: false, message: '请输入身份证号码' };
  }
  if (!isIdCard(value)) {
    return { valid: false, message: '请输入有效的18位身份证号码' };
  }
  return { valid: true, message: '✓ 身份证号码格式正确' };
}

export function validatePassword(value: string): ValidationResult {
  if (!value) {
    return { valid: false, message: '请输入密码' };
  }
  if (value.length < 8) {
    return { valid: false, message: '密码长度至少8位' };
  }
  if (!isPasswordStrong(value)) {
    return { valid: false, message: '密码需要包含大小写字母和数字/特殊字符' };
  }
  return { valid: true, message: '✓ 密码强度符合要求' };
}

export function validateLengthRange(value: string, min: number, max: number): ValidationResult {
  if (!value) {
    return { valid: false, message: '请输入内容' };
  }
  if (value.length < min) {
    return { valid: false, message: `内容长度不能少于${min}个字符` };
  }
  if (value.length > max) {
    return { valid: false, message: `内容长度不能超过${max}个字符` };
  }
  return { valid: true, message: '✓ 长度符合要求' };
}

export function validateNumber(value: string): ValidationResult {
  if (!value) {
    return { valid: false, message: '请输入数字' };
  }
  if (!isNumber(value)) {
    return { valid: false, message: '请输入有效的数字' };
  }
  return { valid: true, message: '✓ 数字格式正确' };
}

export function getPasswordStrength(value: string): PasswordStrength {
  let level = 0;
  
  if (value.length >= 6) level++;
  if (value.length >= 10) level++;
  if (/[a-z]/.test(value)) level++;
  if (/[A-Z]/.test(value)) level++;
  if (/\d/.test(value)) level++;
  if (/[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/.test(value)) level++;
  
  const result: PasswordStrength = { level: level, label: '', color: '' };
  
  if (level <= 2) {
    result.label = '弱';
    result.color = '#FF4757';
  } else if (level <= 4) {
    result.label = '中';
    result.color = '#FFA502';
  } else {
    result.label = '强';
    result.color = '#2ED573';
  }
  
  return result;
}

---、

相关推荐
云草桑1 小时前
.NET10+AI 架构师全套实战学习文档(含源码、案例、面试题、项目源码)
人工智能·学习·ai·.net
风华圆舞1 小时前
SpeechRecognitionChannel 的 Flutter 侧封装思路
flutter·华为·harmonyos
暗夜猎手-大魔王1 小时前
hermes源码学习5-Provider 运行时解析
大数据·人工智能·学习
风满城331 小时前
鸿蒙原生应用实战(二):首页开发 —— Grid分类网格与热歌排行榜
harmonyos
-To be number.wan1 小时前
计算机组成原理 | 指令寻址
学习·计算机组成原理
Niuguangshuo1 小时前
LangChain 学习之旅(二):用 LCEL 与解析器构建标准流水线
学习·langchain·unix
UnicornDev2 小时前
【Flutter x HarmonyOS 6】设置页面的逻辑实现
flutter·华为·harmonyos
The Sheep 20232 小时前
C#多线程学习
开发语言·学习·c#
Swift社区2 小时前
鸿蒙游戏动画系统:架构解析 + Demo实现
游戏·华为·harmonyos