引言

在 OpenHarmony 应用开发中,表单验证是确保数据完整性和准确性的关键环节。随着 React Native 在 OpenHarmony 生态中的普及,我们需要一个既轻量又强大的验证解决方案。今天,我将分享如何构建一个专门为 OpenHarmony + RN 环境优化的 useValidator Hook,实现现代化、声明式的表单验证。

一、OpenHarmony 表单验证的挑战与机遇

1.1 当前困境

  • 验证逻辑分散:业务逻辑与验证代码混杂
  • 平台差异:OpenHarmony 与 iOS/Android 的表单处理差异
  • 性能瓶颈:复杂的验证规则影响应用流畅度
  • 类型安全:JavaScript 的动态特性带来运行时错误风险

1.2 设计目标

// 理想的验证 API
const validator = useValidator({
  rules: {
    username: [
      { required: true, message: '用户名必填' },
      { min: 3, max: 20, message: '长度3-20字符' },
      { pattern: /^[a-zA-Z0-9_]+$/, message: '只能包含字母数字和下划线' }
    ],
    email: [
      { type: 'email', message: '邮箱格式不正确' }
    ]
  },
  // OpenHarmony 特有配置
  harmony: {
    useNativeValidation: true,  // 使用原生验证
    debounceTime: 300          // 防抖时间
  }
});

二、核心架构设计

2.1 TypeScript 类型定义

// types/validator.ts
export type ValidationRule = 
  | { required: boolean; message: string }
  | { min: number; max?: number; message: string }
  | { pattern: RegExp; message: string }
  | { type: 'email' | 'phone' | 'url' | 'idcard'; message: string }
  | { validator: (value: any, values: Record<string, any>) => boolean | Promise<boolean>; message: string }
  | { asyncValidator: (value: any) => Promise<{ valid: boolean; message?: string }> };

export interface ValidationRules {
  [field: string]: ValidationRule[];
}

export interface ValidatorConfig<T = any> {
  rules: ValidationRules;
  initialValues?: T;
  mode?: 'onChange' | 'onBlur' | 'onSubmit' | 'onChangeAfterBlur';
  harmony?: {
    useNativeValidation?: boolean;
    debounceTime?: number;
    autoScrollToError?: boolean;
  };
}

export interface ValidationResult {
  isValid: boolean;
  errors: Record<string, string>;
  warnings: Record<string, string>;
  touched: Record<string, boolean>;
  validate: (field?: string) => Promise<boolean>;
  validateAll: () => Promise<boolean>;
  reset: () => void;
  getFieldProps: (field: string) => FieldProps;
}

2.2 基础 Hook 实现

// hooks/useValidator.ts
import { useState, useCallback, useRef, useEffect } from 'react';
import { Platform } from 'react-native';
import { ValidatorConfig, ValidationResult } from '../types/validator';

const isOpenHarmony = Platform.OS === 'harmony';

export function useValidator<T extends Record<string, any>>({
  rules,
  initialValues = {} as T,
  mode = 'onChangeAfterBlur',
  harmony = {},
}: ValidatorConfig<T>): ValidationResult {
  const [values, setValues] = useState<T>(initialValues);
  const [errors, setErrors] = useState<Record<string, string>>({});
  const [warnings, setWarnings] = useState<Record<string, string>>({});
  const [touched, setTouched] = useState<Record<string, boolean>>({});
  const [isValidating, setIsValidating] = useState(false);
  
  const valuesRef = useRef(values);
  const rulesRef = useRef(rules);
  const timerRef = useRef<NodeJS.Timeout>();
  
  // OpenHarmony 原生验证桥接
  const harmonyValidatorRef = useRef<any>(null);
  
  useEffect(() => {
    if (isOpenHarmony && harmony.useNativeValidation) {
      initHarmonyNativeValidator();
    }
    return () => {
      harmonyValidatorRef.current?.destroy?.();
    };
  }, []);
  
  // 初始化 OpenHarmony 原生验证
  const initHarmonyNativeValidator = async () => {
    try {
      // @ts-ignore
      const { Validator: HarmonyValidator } = await import('@ohos.validator');
      harmonyValidatorRef.current = new HarmonyValidator();
      
      // 将 React 规则转换为 OpenHarmony 原生规则
      const harmonyRules = convertRulesToHarmonyFormat(rules);
      harmonyValidatorRef.current.setRules(harmonyRules);
    } catch (error) {
      console.warn('OpenHarmony 原生验证器加载失败,降级到 JS 验证:', error);
    }
  };

三、验证引擎实现

3.1 内置验证器工厂

// validators/index.ts
export const createValidator = (rule: ValidationRule) => {
  const validatorMap = {
    // 必填验证
    required: (value: any) => {
      if (rule.required) {
        return !(value === undefined || value === null || value === '');
      }
      return true;
    },
    
    // 长度验证
    min: (value: string | any[]) => {
      if ('min' in rule) {
        return value.length >= rule.min;
      }
      return true;
    },
    
    max: (value: string | any[]) => {
      if ('max' in rule) {
        return value.length <= rule.max!;
      }
      return true;
    },
    
    // 正则验证
    pattern: (value: string) => {
      if ('pattern' in rule) {
        return rule.pattern.test(value);
      }
      return true;
    },
    
    // 类型验证
    type: (value: string) => {
      if ('type' in rule) {
        const typeValidators = {
          email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
          phone: /^1[3-9]\d{9}$/,
          url: /^https?:\/\/.+/,
          idcard: /^\d{17}[\dXx]$/,
        };
        const pattern = typeValidators[rule.type];
        return pattern ? pattern.test(value) : true;
      }
      return true;
    },
    
    // 自定义同步验证
    validator: (value: any, values: Record<string, any>) => {
      if ('validator' in rule) {
        return rule.validator(value, values);
      }
      return true;
    },
  };
  
  return (value: any, values: Record<string, any>): boolean => {
    const validatorType = Object.keys(rule)[0] as keyof typeof validatorMap;
    return validatorMap[validatorType]?.(value, values) ?? true;
  };
};

// 异步验证处理器
export const createAsyncValidator = async (
  rule: ValidationRule,
  value: any,
  values: Record<string, any>
): Promise<{ valid: boolean; message?: string }> => {
  if ('asyncValidator' in rule) {
    return await rule.asyncValidator(value);
  }
  return { valid: true };
};

3.2 批量验证与防抖

// hooks/useValidator.ts (续)
// 单字段验证
const validateField = useCallback(async (
  field: string,
  value?: any
): Promise<string | null> => {
  const fieldValue = value ?? valuesRef.current[field];
  const fieldRules = rulesRef.current[field];
  
  if (!fieldRules?.length) return null;
  
  // 优先使用 OpenHarmony 原生验证
  if (isOpenHarmony && harmonyValidatorRef.current) {
    try {
      const result = await harmonyValidatorRef.current.validateField(
        field,
        fieldValue
      );
      if (!result.valid) return result.message;
    } catch (error) {
      // 降级到 JS 验证
    }
  }
  
  // JS 验证逻辑
  for (const rule of fieldRules) {
    const validator = createValidator(rule);
    const isValid = validator(fieldValue, valuesRef.current);
    
    if (!isValid) {
      // @ts-ignore
      return rule.message;
    }
    
    // 异步验证
    if ('asyncValidator' in rule) {
      const result = await createAsyncValidator(rule, fieldValue, valuesRef.current);
      if (!result.valid) return result.message || '验证失败';
    }
  }
  
  return null;
}, []);

// 批量验证(带防抖)
const debouncedValidateAll = useCallback(() => {
  if (timerRef.current) {
    clearTimeout(timerRef.current);
  }
  
  timerRef.current = setTimeout(async () => {
    setIsValidating(true);
    const newErrors: Record<string, string> = {};
    
    for (const field in rulesRef.current) {
      const error = await validateField(field);
      if (error) {
        newErrors[field] = error;
      }
    }
    
    setErrors(newErrors);
    setIsValidating(false);
  }, harmony.debounceTime ?? 300);
}, [harmony.debounceTime, validateField]);

// 实时验证(根据 mode 配置)
useEffect(() => {
  if (mode === 'onChange') {
    debouncedValidateAll();
  }
}, [values, mode, debouncedValidateAll]);

四、OpenHarmony 深度集成

4.1 原生验证桥接

// harmony/validator-bridge.ts
import { promptAction } from '@ohos.promptAction';

export class HarmonyValidator {
  private rules: Record<string, any> = {};
  
  setRules(rules: Record<string, any>) {
    this.rules = rules;
  }
  
  async validateField(field: string, value: any): Promise<{
    valid: boolean;
    message?: string;
    nativeError?: any;
  }> {
    try {
      // 使用 OpenHarmony 内置验证能力
      // @ts-ignore
      const { validate } = await import('@system.prompt');
      
      const fieldRules = this.rules[field];
      if (!fieldRules) return { valid: true };
      
      for (const rule of fieldRules) {
        if (rule.required && !value) {
          await promptAction.showToast({ message: rule.message });
          return { valid: false, message: rule.message };
        }
        
        if (rule.type === 'phone') {
          // 使用 OpenHarmony 的电话号码验证
          // @ts-ignore
          const { PhoneNumberUtils } = await import('@ohos.telephony.data');
          const isValid = PhoneNumberUtils.isPhoneNumber(value);
          if (!isValid) {
            return { valid: false, message: rule.message };
          }
        }
      }
      
      return { valid: true };
    } catch (error) {
      console.error('OpenHarmony 原生验证失败:', error);
      throw error;
    }
  }
  
  destroy() {
    this.rules = {};
  }
}

// React 规则转换器
export const convertRulesToHarmonyFormat = (
  reactRules: ValidationRules
): Record<string, any[]> => {
  return Object.entries(reactRules).reduce((acc, [field, rules]) => {
    acc[field] = rules.map(rule => {
      if ('required' in rule) {
        return { type: 'required', message: rule.message };
      }
      if ('pattern' in rule) {
        return { type: 'regex', pattern: rule.pattern.source, message: rule.message };
      }
      if ('type' in rule) {
        return { type: rule.type, message: rule.message };
      }
      return rule;
    });
    return acc;
  }, {} as Record<string, any[]>);
};

4.2 智能错误提示

// harmony/error-handler.ts
import { promptAction } from '@ohos.promptAction';

export class HarmonyErrorHandler {
  private lastErrorTime: number = 0;
  private errorDebounceTime: 1000;
  
  async showError(field: string, message: string) {
    const now = Date.now();
    if (now - this.lastErrorTime < this.errorDebounceTime) {
      return;
    }
    
    this.lastErrorTime = now;
    
    // OpenHarmony 原生错误提示
    try {
      await promptAction.showDialog({
        title: '验证错误',
        message: `${field}: ${message}`,
        buttons: [{ text: '确定', color: '#007DFF' }]
      });
    } catch (error) {
      // 降级方案
      console.error(`[${field}] ${message}`);
    }
  }
  
  // 自动滚动到错误字段
  async scrollToError(field: string, componentRef: any) {
    if (!componentRef || !field) return;
    
    try {
      // @ts-ignore
      const { scroll } = await import('@ohos.arkui.component');
      scroll.scrollTo(componentRef, {
        x: 0,
        y: componentRef.offsetTop - 100,
        animated: true
      });
    } catch (error) {
      console.warn('自动滚动失败:', error);
    }
  }
}

五、完整使用示例

5.1 基础表单验证

// RegisterForm.jsx
import React, { useRef } from 'react';
import { 
  View, 
  Text, 
  TextInput, 
  Button, 
  ScrollView 
} from 'react-native';
import { useValidator } from './hooks/useValidator';

const RegisterForm = () => {
  const scrollViewRef = useRef();
  
  const validator = useValidator({
    rules: {
      username: [
        { required: true, message: '用户名不能为空' },
        { min: 3, max: 20, message: '用户名长度3-20字符' },
        { 
          pattern: /^[a-zA-Z][a-zA-Z0-9_]*$/, 
          message: '以字母开头,只能包含字母数字和下划线' 
        },
        {
          asyncValidator: async (value) => {
            // 异步检查用户名是否可用
            const response = await fetch(
              `https://api.example.com/check-username?username=${value}`
            );
            const { available } = await response.json();
            return {
              valid: available,
              message: available ? undefined : '用户名已存在'
            };
          }
        }
      ],
      password: [
        { required: true, message: '密码不能为空' },
        { min: 8, message: '密码至少8位' },
        { 
          pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, 
          message: '必须包含大小写字母和数字' 
        }
      ],
      confirmPassword: [
        { 
          validator: (value, values) => value === values.password,
          message: '两次密码不一致'
        }
      ],
      email: [
        { required: true, message: '邮箱不能为空' },
        { type: 'email', message: '邮箱格式不正确' }
      ],
      phone: [
        { type: 'phone', message: '手机号格式不正确' }
      ]
    },
    mode: 'onChangeAfterBlur',
    harmony: {
      useNativeValidation: true,
      debounceTime: 500,
      autoScrollToError: true
    }
  });
  
  const handleSubmit = async () => {
    const isValid = await validator.validateAll();
    if (isValid) {
      console.log('表单数据:', validator.values);
      // 提交逻辑...
    }
  };
  
  return (
    <ScrollView 
      ref={scrollViewRef}
      style={styles.container}
    >
      {['username', 'password', 'confirmPassword', 'email', 'phone'].map(field => {
        const fieldProps = validator.getFieldProps(field);
        
        return (
          <View key={field} style={styles.fieldContainer}>
            <Text style={styles.label}>
              {field === 'confirmPassword' ? '确认密码' : 
               field === 'phone' ? '手机号' : field}
            </Text>
            
            <TextInput
              style={[
                styles.input,
                fieldProps.touched && fieldProps.error && styles.inputError
              ]}
              placeholder={`请输入${fieldProps.label}`}
              value={fieldProps.value}
              onChangeText={fieldProps.onChange}
              onBlur={fieldProps.onBlur}
              secureTextEntry={field.includes('password')}
              keyboardType={
                field === 'email' ? 'email-address' :
                field === 'phone' ? 'phone-pad' : 'default'
              }
            />
            
            {fieldProps.touched && fieldProps.error && (
              <Text style={styles.errorText}>
                {fieldProps.error}
              </Text>
            )}
          </View>
        );
      })}
      
      <View style={styles.buttonContainer}>
        <Button
          title="注册"
          onPress={handleSubmit}
          disabled={validator.isValidating || !validator.isValid}
        />
        
        <Button
          title="重置"
          onPress={validator.reset}
          color="#999"
        />
      </View>
    </ScrollView>
  );
};

const styles = {
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#fff'
  },
  fieldContainer: {
    marginBottom: 20
  },
  label: {
    fontSize: 16,
    fontWeight: '600',
    marginBottom: 8,
    color: '#333'
  },
  input: {
    height: 48,
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    paddingHorizontal: 12,
    fontSize: 16
  },
  inputError: {
    borderColor: '#ff4d4f'
  },
  errorText: {
    color: '#ff4d4f',
    fontSize: 14,
    marginTop: 4
  },
  buttonContainer: {
    marginTop: 30,
    gap: 12
  }
};

export default RegisterForm;

5.2 复杂业务场景

// OrderForm.jsx - 动态表单验证
import React from 'react';
import { useValidator } from './hooks/useValidator';
import { ValidationRule } from './types/validator';

const OrderForm = () => {
  // 动态规则生成
  const generateRules = (productType: string): ValidationRule[] => {
    const baseRules: ValidationRule[] = [
      { required: true, message: '产品名称必填' }
    ];
    
    if (productType === 'electronic') {
      return [
        ...baseRules,
        { 
          validator: (value) => value.length <= 50,
          message: '电子产品名称不能超过50字' 
        }
      ];
    }
    
    if (productType === 'food') {
      return [
        ...baseRules,
        { 
          type: 'custom',
          validator: (value) => !value.includes('过期'),
          message: '产品名称不合法' 
        }
      ];
    }
    
    return baseRules;
  };
  
  const validator = useValidator({
    rules: {
      productName: generateRules('electronic'),
      quantity: [
        { 
          validator: (value) => value > 0 && value <= 1000,
          message: '数量必须在1-1000之间' 
        }
      ],
      deliveryDate: [
        {
          validator: (value) => {
            const today = new Date();
            const selectedDate = new Date(value);
            return selectedDate > today;
          },
          message: '配送日期必须晚于今天'
        }
      ]
    },
    harmony: {
      // OpenHarmony 日历集成
      calendarIntegration: true
    }
  });
  
  return (
    // 表单实现...
  );
};

六、高级特性

6.1 跨字段验证

// hooks/useCrossFieldValidator.ts
export const useCrossFieldValidator = (
  validator: ValidationResult
) => {
  const validateDependencies = useCallback((field: string) => {
    const dependencies = {
      password: ['confirmPassword'],
      startDate: ['endDate'],
      minValue: ['maxValue']
    };
    
    const relatedFields = dependencies[field] || [];
    relatedFields.forEach(async (relatedField) => {
      await validator.validate(relatedField);
    });
  }, [validator]);
  
  return { validateDependencies };
};

// 在 useValidator 中集成
const crossFieldValidator = useCrossFieldValidator(validator);

6.2 验证规则组合

// utils/rule-composer.ts
export const composeRules = (...ruleSets: ValidationRules[]) => {
  return ruleSets.reduce((acc, rules) => {
    Object.entries(rules).forEach(([field, fieldRules]) => {
      if (!acc[field]) {
        acc[field] = [];
      }
      acc[field].push(...fieldRules);
    });
    return acc;
  }, {} as ValidationRules);
};

// 使用示例
const baseRules = {
  username: [{ required: true }]
};

const businessRules = {
  username: [{ pattern: /^[a-z]+$/ }],
  age: [{ min: 18 }]
};

const finalRules = composeRules(baseRules, businessRules);

七、性能优化与测试

7.1 性能监控

// hooks/useValidator.performance.ts
import { PerformanceMonitor } from '@ohos.performance.monitor';

export const useValidatorPerformance = (validator: ValidationResult) => {
  useEffect(() => {
    const monitor = new PerformanceMonitor('form_validator');
    
    monitor.startMonitoring('validation_time', {
      sampleRate: 0.1, // 10%采样率
      reportInterval: 10000 // 10秒上报一次
    });
    
    return () => {
      monitor.stopMonitoring();
    };
  }, []);
  
  // 验证耗时统计
  const validateWithMetrics = useCallback(async (field?: string) => {
    const startTime = performance.now();
    const result = field 
      ? await validator.validate(field)
      : await validator.validateAll();
    const endTime = performance.now();
    
    // 上报性能数据
    reportMetric('validation_duration', endTime - startTime);
    
    return result;
  }, [validator]);
  
  return { validateWithMetrics };
};

7.2 单元测试

// __tests__/useValidator.test.ts
import { renderHook, act } from '@testing-library/react-hooks';
import { useValidator } from '../hooks/useValidator';
import { Platform } from 'react-native';

describe('useValidator in OpenHarmony', () => {
  beforeEach(() => {
    Platform.OS = 'harmony';
  });
  
  test('应该正确处理必填验证', async () => {
    const { result, waitForNextUpdate } = renderHook(() =>
      useValidator({
        rules: {
          username: [{ required: true, message: '必填' }]
        }
      })
    );
    
    await act(async () => {
      const isValid = await result.current.validate('username');
      expect(isValid).toBe(false);
      expect(result.current.errors.username).toBe('必填');
    });
  });
  
  test('应该支持异步验证', async () => {
    const asyncRule = {
      asyncValidator: async (value: string) => ({
        valid: value === 'available',
        message: '用户名已存在'
      })
    };
    
    const { result } = renderHook(() =>
      useValidator({
        rules: {
          username: [asyncRule]
        }
      })
    );
    
    await act(async () => {
      result.current.setFieldValue('username', 'taken');
      const isValid = await result.current.validate('username');
      expect(isValid).toBe(false);
    });
  });
});

八、最佳实践与总结

8.1 OpenHarmony 特有优化

  1. 原生能力优先:尽量使用 OpenHarmony 原生验证能力
  2. 离线验证:支持无网络环境下的基本验证
  3. 内存管理:及时清理验证器实例
  4. 无障碍支持:为视障用户提供语音验证反馈

8.2 性能优化建议

// 规则懒加载
const lazyRules = useMemo(() => ({
  complexField: shouldValidateComplex 
    ? complexRules 
    : basicRules
}), [shouldValidateComplex]);

// 验证缓存
const validationCache = useRef(new Map());

8.3 扩展建议

  1. 国际化:支持多语言错误消息
  2. 主题适配:跟随系统主题切换验证样式
  3. AI 增强:智能建议和自动修正
  4. 区块链验证:重要数据的链上验证存证

结语

通过本文的实现,我们构建了一个强大、灵活且专门针对 OpenHarmony + RN 环境优化的 useValidator Hook。它不仅提供了完整的验证功能,还深度集成了 OpenHarmony 的原生能力,确保了最佳的性能和用户体验。

这个解决方案的特点包括:

  • 🚀 高性能:智能防抖、原生加速
  • 🔧 高扩展:插件化架构、规则组合
  • 📱 平台优化:OpenHarmony 深度集成
  • 🛡 类型安全:完整的 TypeScript 支持
  • 🧪 测试完备:单元测试、性能监控

希望这个实现能为你的 OpenHarmony 应用开发带来帮助,让表单验证不再是痛点,而是应用的亮点!


相关资源

欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐