前言

本文详细介绍了在 OpenHarmony 6.0.0 平台上运用 React Native 开发自定义表单管理 Hook (useForm) 的完整解决方案。该方案通过 useForm 的设计,有效解决了表单状态管理、验证规则和提交处理等核心问题,同时针对 OpenHarmony 6.0.0 平台的特性需求进行了专门优化。

一、表单管理需求分析

1.1 传统表单管理存在的问题

以下是转换后的表格形式:

痛点 描述 影响
状态分散 表单字段状态分散在组件各处 代码维护困难
验证冗余 需要为每个字段单独编写验证逻辑 代码重复率高
提交耦合 提交逻辑与 UI 组件紧密耦合 难以复用
跨平台差异 不同平台输入行为不一致 兼容性问题

** 1.2 OpenHarmony 平台特性考量
**

┌─────────────────────────────────────────────────────────────────┐
│                  OpenHarmony 表单特殊考量                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────┐ │
│  │  输入组件行为   │    │  性能优化需求   │    │ 异步提交处理 │ │
│  │                 │    │                 │    │             │ │
│  │ • 焦点管理差异  │    │ • 频繁状态更新  │    │ • 网络模型   │ │
│  │ • 键盘行为不同  │    │ • 渲染性能敏感  │    │ • 超时处理   │ │
│  │ • 输入法处理    │    │ • 批量更新需求  │    │ • 重试机制   │ │
│  └─────────────────┘    └─────────────────┘    └─────────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

二、 TypeScript 类型系统架构设计

// ============================================
// types/form.ts
// 表单管理类型定义
// ============================================

/**
 * 验证规则接口
 */
export interface ValidationRule<T = any> {
  /** 必填验证 */
  required?: boolean;
  /** 最小长度 */
  minLength?: number;
  /** 最大长度 */
  maxLength?: number;
  /** 最小值 */
  min?: number;
  /** 最大值 */
  max?: number;
  /** 正则表达式验证 */
  pattern?: RegExp;
  /** 自定义同步验证函数 */
  validate?: (value: T, formData?: Record<string, any>) => string | null | boolean;
  /** 自定义错误消息 */
  message?: string;
}

/**
 * 字段规则配置(支持多规则)
 */
export type FieldRules<T = any> = ValidationRule<T> | ValidationRule<T>[];

/**
 * 表单验证规则映射
 */
export type FormValidationRules<T extends Record<string, any>> = {
  [K in keyof T]?: FieldRules<T[K]>;
};

/**
 * 表单状态接口
 */
export interface FormState<T extends Record<string, any> = Record<string, any>> {
  /** 表单数据 */
  values: T;
  /** 错误信息 */
  errors: Partial<Record<keyof T, string | null>>;
  /** 触碰状态(是否被聚焦过) */
  touched: Partial<Record<keyof T, boolean>>;
  /** 是否已被修改(脏数据) */
  isDirty: boolean;
  /** 是否验证通过 */
  isValid: boolean;
  /** 是否正在提交 */
  isSubmitting: boolean;
  /** 提交次数 */
  submitCount: number;
}

/**
 * 表单操作接口
 */
export interface FormActions<T extends Record<string, any> = Record<string, any>> {
  /** 设置字段值 */
  setValue: <K extends keyof T>(field: K, value: T[K]) => void;
  /** 批量设置字段值 */
  setValues: (values: Partial<T>) => void;
  /** 设置错误信息 */
  setError: <K extends keyof T>(field: K, error: string | null) => void;
  /** 批量设置错误信息 */
  setErrors: (errors: Partial<Record<keyof T, string | null>>) => void;
  /** 设置触碰状态 */
  setTouched: <K extends keyof T>(field: K, touched: boolean) => void;
  /** 设置提交状态 */
  setSubmitting: (isSubmitting: boolean) => void;
  /** 验证单个字段 */
  validateField: <K extends keyof T>(field: K) => Promise<boolean>;
  /** 验证所有字段 */
  validate: () => Promise<boolean>;
  /** 重置表单 */
  reset: (values?: Partial<T>) => void;
  /** 清除所有错误 */
  clearErrors: () => void;
}

/**
 * 表单返回值接口
 */
export interface UseFormReturn<T extends Record<string, any> = Record<string, any>>
  extends FormState<T>,
    FormActions<T> {
  /** 字段变化处理 */
  handleChange: <K extends keyof T>(field: K) => (value: T[K]) => void;
  /** 字段失焦处理 */
  handleBlur: <K extends keyof T>(field: K) => () => void;
  /** 提交处理 */
  handleSubmit: (
    onSubmit: (values: T, actions: FormActions<T>) => void | Promise<void>
  ) => (e?: any) => Promise<void>;
}

/**
 * useForm 配置选项
 */
export interface UseFormOptions<T extends Record<string, any>> {
  /** 初始值 */
  initialValues: T;
  /** 验证规则 */
  validationRules?: FormValidationRules<T>;
  /** 验证触发时机 */
  mode?: 'onBlur' | 'onChange' | 'onChangeBlur' | 'all';
  /** 验证防抖延迟(毫秒) */
  debounceDelay?: number;
  /** 提交前是否验证 */
  validateOnSubmit?: boolean;
  /** 重置时是否重置到初始值 */
  resetToInitial?: boolean;
}

/**
 * 验证结果类型
 */
export type ValidationResult = string | null | boolean;

/**
 * 字段验证器类型
 */
export type FieldValidator<T = any> = (
  value: T,
  formData?: Record<string, any>
) => ValidationResult | Promise<ValidationResult>;

三、核心 useForm Hook 实现方案

// ============================================
// hooks/useForm.ts
// 表单管理 Hook
// ============================================

import { useState, useCallback, useRef, useEffect } from 'react';
import type {
  UseFormOptions,
  UseFormReturn,
  FormState,
  FormActions,
  ValidationRule,
  FieldValidator,
  ValidationResult
} from '../types/form';
import { createFieldValidator } from '../utils/validators';

/**
 * 表单管理 Hook
 * @param options 表单配置选项
 * @returns 表单状态和方法
 */
export function useForm<T extends Record<string, any>>(
  options: UseFormOptions<T>
): UseFormReturn<T> {
  const {
    initialValues,
    validationRules = {},
    mode = 'onBlur',
    debounceDelay = 300,
    validateOnSubmit = true,
    resetToInitial = true
  } = options;

  // 保存初始值的引用
  const initialValuesRef = useRef(initialValues);
  const debounceTimersRef = useRef<Map<keyof T, ReturnType<typeof setTimeout>>>(new Map());
  const submittingRef = useRef(false);

  // 表单状态
  const [state, setState] = useState<FormState<T>>({
    values: { ...initialValues },
    errors: {},
    touched: {},
    isDirty: false,
    isValid: true,
    isSubmitting: false,
    submitCount: 0
  });

  /**
   * 验证单个字段
   */
  const validateField = useCallback(
    async <K extends keyof T>(field: K): Promise<boolean> => {
      const rules = validationRules[field];
      const value = state.values[field];

      if (!rules) {
        // 无规则则清除错误
        setState(prev => ({
          ...prev,
          errors: { ...prev.errors, [field]: null }
        }));
        return true;
      }

      // 执行验证
      const result = await executeValidation(value, rules, state.values);
      const isValid = result === null || result === true;
      const error = !isValid ? (typeof result === 'string' ? result : null) : null;

      setState(prev => ({
        ...prev,
        errors: { ...prev.errors, [field]: error },
        isValid: Object.keys(prev.errors).reduce((acc, key) => {
          if (key === String(field)) return acc && isValid;
          return acc && prev.errors[key as keyof T] === null;
        }, true)
      }));

      return isValid;
    },
    [state.values, validationRules]
  );

  /**
   * 验证所有字段
   */
  const validate = useCallback(async (): Promise<boolean> => {
    const fieldNames = Object.keys(validationRules) as Array<keyof T>;
    const results = await Promise.all(
      fieldNames.map(field => validateField(field))
    );
    return results.every(r => r);
  }, [validationRules, validateField]);

  /**
   * 设置字段值
   */
  const setValue = useCallback(
    <K extends keyof T>(field: K, value: T[K]) => {
      setState(prev => {
        const newValues = { ...prev.values, [field]: value };
        const isDirty =
          JSON.stringify(newValues) !== JSON.stringify(initialValuesRef.current);

        return {
          ...prev,
          values: newValues,
          isDirty
        };
      });

      // 根据模式决定是否验证
      if (mode === 'onChange' || mode === 'onChangeBlur' || mode === 'all') {
        // 防抖验证
        const timer = debounceTimersRef.current.get(field);
        if (timer) clearTimeout(timer);

        debounceTimersRef.current.set(
          field,
          setTimeout(() => {
            validateField(field);
            debounceTimersRef.current.delete(field);
          }, debounceDelay)
        );
      }
    },
    [mode, debounceDelay, validateField]
  );

  /**
   * 批量设置字段值
   */
  const setValues = useCallback((values: Partial<T>) => {
    setState(prev => {
      const newValues = { ...prev.values, ...values };
      const isDirty =
        JSON.stringify(newValues) !== JSON.stringify(initialValuesRef.current);

      return {
        ...prev,
        values: newValues,
        isDirty
      };
    });
  }, []);

  /**
   * 设置错误信息
   */
  const setError = useCallback(<K extends keyof T>(field: K, error: string | null) => {
    setState(prev => ({
      ...prev,
      errors: { ...prev.errors, [field]: error }
    }));
  }, []);

  /**
   * 批量设置错误信息
   */
  const setErrors = useCallback((errors: Partial<Record<keyof T, string | null>>) => {
    setState(prev => ({
      ...prev,
      errors: { ...prev.errors, ...errors }
    }));
  }, []);

  /**
   * 设置触碰状态
   */
  const setTouched = useCallback(<K extends keyof T>(field: K, touched: boolean) => {
    setState(prev => ({
      ...prev,
      touched: { ...prev.touched, [field]: touched }
    }));

    // 根据模式决定是否验证
    if (mode === 'onBlur' || mode === 'onChangeBlur' || mode === 'all') {
      validateField(field);
    }
  }, [mode, validateField]);

  /**
   * 设置提交状态
   */
  const setSubmitting = useCallback((isSubmitting: boolean) => {
    submittingRef.current = isSubmitting;
    setState(prev => ({
      ...prev,
      isSubmitting
    }));
  }, []);

  /**
   * 重置表单
   */
  const reset = useCallback((values?: Partial<T>) => {
    // 清除所有定时器
    debounceTimersRef.current.forEach(timer => clearTimeout(timer));
    debounceTimersRef.current.clear();

    const resetValues = values
      ? { ...initialValuesRef.current, ...values }
      : resetToInitial
      ? { ...initialValuesRef.current }
      : state.values;

    setState({
      values: resetValues,
      errors: {},
      touched: {},
      isDirty: false,
      isValid: true,
      isSubmitting: false,
      submitCount: 0
    });
  }, [resetToInitial, state.values]);

  /**
   * 清除所有错误
   */
  const clearErrors = useCallback(() => {
    setState(prev => ({
      ...prev,
      errors: {},
      isValid: true
    }));
  }, []);

  /**
   * 字段变化处理
   */
  const handleChange = useCallback(
    <K extends keyof T>(field: K) => (value: T[K]) => {
      setValue(field, value);
    },
    [setValue]
  );

  /**
   * 字段失焦处理
   */
  const handleBlur = useCallback(
    <K extends keyof T>(field: K) => () => {
      setTouched(field, true);
    },
    [setTouched]
  );

  /**
   * 提交处理
   */
  const handleSubmit = useCallback(
    (
      onSubmit: (values: T, actions: FormActions<T>) => void | Promise<void>
    ) => async (e?: any) => {
      e?.preventDefault();

      // 验证表单
      const isValid = validateOnSubmit ? await validate() : true;

      if (!isValid) {
        setState(prev => ({
          ...prev,
          submitCount: prev.submitCount + 1
        }));
        return;
      }

      // 执行提交
      setSubmitting(true);
      setState(prev => ({
        ...prev,
        submitCount: prev.submitCount + 1
      }));

      try {
        const actions: FormActions<T> = {
          setValue,
          setValues,
          setError,
          setErrors,
          setTouched,
          setSubmitting,
          validateField,
          validate,
          reset,
          clearErrors
        };

        await onSubmit(state.values, actions);
      } catch (error) {
        console.error('Form submission error:', error);
        throw error;
      } finally {
        setSubmitting(false);
      }
    },
    [validate, validateOnSubmit, state.values, setSubmitting, setValue, setValues, setError, setErrors, setTouched, validateField, reset, clearErrors]
  );

  // 组件卸载时清理定时器
  useEffect(() => {
    return () => {
      debounceTimersRef.current.forEach(timer => clearTimeout(timer));
      debounceTimersRef.current.clear();
    };
  }, []);

  return {
    ...state,
    setValue,
    setValues,
    setError,
    setErrors,
    setTouched,
    setSubmitting,
    validateField,
    validate,
    reset,
    clearErrors,
    handleChange,
    handleBlur,
    handleSubmit
  };
}

/**
 * 执行验证规则
 */
async function executeValidation<T>(
  value: T,
  rules: ValidationRule<T> | ValidationRule<T>[],
  formData: Record<string, any>
): Promise<ValidationResult> {
  const rulesArray = Array.isArray(rules) ? rules : [rules];

  for (const rule of rulesArray) {
    // 必填验证
    if (rule.required !== undefined && rule.required) {
      const requiredResult = validateRequired(value);
      if (requiredResult !== null) {
        return rule.message || requiredResult;
      }
    }

    // 如果值为空且非必填,跳过其他验证
    if (isEmpty(value)) {
      return null;
    }

    // 最小长度验证
    if (rule.minLength !== undefined) {
      const result = validateMinLength(value, rule.minLength);
      if (result !== null) return rule.message || result;
    }

    // 最大长度验证
    if (rule.maxLength !== undefined) {
      const result = validateMaxLength(value, rule.maxLength);
      if (result !== null) return rule.message || result;
    }

    // 最小值验证
    if (rule.min !== undefined) {
      const result = validateMin(value, rule.min);
      if (result !== null) return rule.message || result;
    }

    // 最大值验证
    if (rule.max !== undefined) {
      const result = validateMax(value, rule.max);
      if (result !== null) return rule.message || result;
    }

    // 正则表达式验证
    if (rule.pattern) {
      const result = validatePattern(value, rule.pattern);
      if (result !== null) return rule.message || result;
    }

    // 自定义验证
    if (rule.validate) {
      const result = await rule.validate(value, formData);
      if (result !== true && result !== null) {
        return typeof result === 'string' ? result : rule.message || '验证失败';
      }
    }
  }

  return null;
}

/**
 * 验证器函数集合
 */
function validateRequired<T>(value: T): ValidationResult {
  if (value === null || value === undefined) return '此字段为必填项';
  if (typeof value === 'string' && value.trim() === '') return '此字段为必填项';
  if (Array.isArray(value) && value.length === 0) return '至少选择一项';
  return null;
}

function isEmpty<T>(value: T): boolean {
  if (value === null || value === undefined) return true;
  if (typeof value === 'string' && value.trim() === '') return true;
  if (Array.isArray(value) && value.length === 0) return true;
  return false;
}

function validateMinLength<T>(value: T, min: number): ValidationResult {
  const length = getValueLength(value);
  return length < min ? `长度不能少于 ${min} 个字符` : null;
}

function validateMaxLength<T>(value: T, max: number): ValidationResult {
  const length = getValueLength(value);
  return length > max ? `长度不能超过 ${max} 个字符` : null;
}

function validateMin<T>(value: T, min: number): ValidationResult {
  if (typeof value === 'number') return value < min ? `不能小于 ${min}` : null;
  if (typeof value === 'string') {
    const num = parseFloat(value);
    return isNaN(num) || num < min ? `不能小于 ${min}` : null;
  }
  return null;
}

function validateMax<T>(value: T, max: number): ValidationResult {
  if (typeof value === 'number') return value > max ? `不能大于 ${max}` : null;
  if (typeof value === 'string') {
    const num = parseFloat(value);
    return isNaN(num) || num > max ? `不能大于 ${max}` : null;
  }
  return null;
}

function validatePattern<T>(value: T, pattern: RegExp): ValidationResult {
  const strValue = String(value);
  return !pattern.test(strValue) ? '格式不正确' : null;
}

function getValueLength<T>(value: T): number {
  if (value === null || value === undefined) return 0;
  if (typeof value === 'string' || Array.isArray(value)) return value.length;
  if (typeof value === 'number') return String(value).length;
  return String(value).length;
}

/**
 * 创建字段验证器
 */
export function createFieldValidator<T>(
  rules: ValidationRule<T> | ValidationRule<T>[]
): FieldValidator<T> {
  return (value: T, formData?: Record<string, any>) => {
    return executeValidation(value, rules, formData || {});
  };
}

/**
 * 常用正则表达式预设
 */
export const ValidationPatterns = {
  /** 电子邮箱 */
  email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
  /** 手机号(中国大陆) */
  phone: /^1[3-9]\d{9}$/,
  /** 身份证号(中国大陆) */
  idCard: /^[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]$/,
  /** URL */
  url: /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/,
  /** 用户名 */
  username: /^[a-zA-Z0-9_]{3,20}$/,
  /** 密码(强) */
  passwordStrong: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
  /** 密码(基础) */
  passwordBasic: /^.{6,}$/,
  /** 数字 */
  number: /^-?\d+(\.\d+)?$/,
  /** 正整数 */
  positiveInteger: /^[1-9]\d*$/,
} as const;

四、 表单验证功能实现

// ============================================
// utils/validators.ts
// 表单验证工具
// ============================================

import type { ValidationRule, ValidationResult } from '../types/form';

/**
 * 验证器类
 * 提供静态方法执行各种验证
 */
export class Validator {
  /**
   * 必填验证
   */
  static required(message?: string): ValidationRule {
    return {
      required: true,
      message: message || '此字段为必填项'
    };
  }

  /**
   * 最小长度验证
   */
  static minLength(min: number, message?: string): ValidationRule {
    return {
      minLength: min,
      message: message || `长度不能少于 ${min} 个字符`
    };
  }

  /**
   * 最大长度验证
   */
  static maxLength(max: number, message?: string): ValidationRule {
    return {
      maxLength: max,
      message: message || `长度不能超过 ${max} 个字符`
    };
  }

  /**
   * 范围长度验证
   */
  static length(min: number, max: number, message?: string): ValidationRule {
    return {
      minLength: min,
      maxLength: max,
      message: message || `长度必须在 ${min}${max} 个字符之间`
    };
  }

  /**
   * 最小值验证
   */
  static min(min: number, message?: string): ValidationRule {
    return {
      min,
      message: message || `不能小于 ${min}`
    };
  }

  /**
   * 最大值验证
   */
  static max(max: number, message?: string): ValidationRule {
    return {
      max,
      message: message || `不能大于 ${max}`
    };
  }

  /**
   * 数值范围验证
   */
  static range(min: number, max: number, message?: string): ValidationRule {
    return {
      min,
      max,
      message: message || `必须在 ${min}${max} 之间`
    };
  }

  /**
   * 正则表达式验证
   */
  static pattern(regex: RegExp, message?: string): ValidationRule {
    return {
      pattern: regex,
      message: message || '格式不正确'
    };
  }

  /**
   * 电子邮箱验证
   */
  static email(message?: string): ValidationRule {
    return this.pattern(
      /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
      message || '请输入有效的邮箱地址'
    );
  }

  /**
   * 手机号验证
   */
  static phone(message?: string): ValidationRule {
    return this.pattern(
      /^1[3-9]\d{9}$/,
      message || '请输入有效的手机号码'
    );
  }

  /**
   * URL 验证
   */
  static url(message?: string): ValidationRule {
    return this.pattern(
      /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/,
      message || '请输入有效的 URL'
    );
  }

  /**
   * 自定义验证函数
   */
  static custom<T = any>(
    validator: (value: T, formData?: Record<string, any>) => ValidationResult,
    message?: string
  ): ValidationRule<T> {
    return {
      validate: validator,
      message
    };
  }

  /**
   * 组合多个验证规则
   */
  static combine<T = any>(...rules: ValidationRule<T>[]): ValidationRule<T>[] {
    return rules;
  }

  /**
   * 条件验证
   */
  static when<T = any>(
    condition: (value: T, formData?: Record<string, any>) => boolean,
    rules: ValidationRule | ValidationRule[]
  ): ValidationRule<T> {
    return {
      validate: (value, formData) => {
        if (!condition(value, formData)) return true;
        // 这里简化处理,实际应该递归执行规则
        return true;
      }
    };
  }
}

/**
 * 创建异步验证器
 */
export function createAsyncValidator<T>(
  asyncValidator: (value: T) => Promise<ValidationResult>,
  delay: number = 500
): ValidationRule<T> {
  return {
    validate: async (value: T) => {
      // 模拟网络延迟
      await new Promise(resolve => setTimeout(resolve, delay));
      return asyncValidator(value);
    }
  };
}

/**
 * 常用验证器预设
 */
export const Validators = {
  /** 必填 */
  required: (message?: string) => Validator.required(message),
  /** 邮箱 */
  email: (message?: string) => Validator.email(message),
  /** 手机号 */
  phone: (message?: string) => Validator.phone(message),
  /** URL */
  url: (message?: string) => Validator.url(message),
  /** 用户名 */
  username: (message?: string) =>
    Validator.pattern(/^[a-zA-Z0-9_]{3,20}$/, message || '用户名必须是3-20位字母、数字或下划线'),
  /** 密码(基础) */
  password: (message?: string) =>
    Validator.pattern(/^.{6,}$/, message || '密码至少6位'),
  /** 密码(强) */
  passwordStrong: (message?: string) =>
    Validator.pattern(
      /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
      message || '密码必须包含大小写字母和数字,至少8位'
    ),
  /** 确认密码 */
  confirmPassword: (passwordFieldName: string, message?: string): ValidationRule => ({
    validate: (value: string, formData) =>
      value === formData[passwordFieldName] || message || '两次输入的密码不一致'
  })
} as const;

/**
 * 跨字段验证器
 */
export class CrossFieldValidator {
  /**
   * 字段匹配验证(如确认密码)
   */
  static match(
    targetField: string,
    message?: string
  ): ValidationRule {
    return {
      validate: (value: any, formData?: Record<string, any>) => {
        const targetValue = formData?.[targetField];
        return value === targetValue || message || `必须与 ${targetField} 一致`;
      }
    };
  }

  /**
   * 字段范围验证(如开始日期必须早于结束日期)
   */
  static before(
    targetField: string,
    message?: string
  ): ValidationRule<Date> {
    return {
      validate: (value: Date, formData?: Record<string, any>) => {
        const targetValue = formData?.[targetField];
        if (!value || !targetValue) return true;
        return value <= targetValue || message || `必须在 ${targetField} 之前`;
      }
    };
  }

  /**
   * 字段依赖验证(如当选择了A时,B必填)
   */
  static requiredWhen(
    condition: (formData: Record<string, any>) => boolean,
    message?: string
  ): ValidationRule {
    return {
      validate: (value: any, formData?: Record<string, any>) => {
        if (!condition(formData || {})) return true;
        return Validator.required().validate?.(value) || message || '此字段为必填项';
      }
    };
  }
}

五、 OpenHarmony 平台适配

// ============================================
// platform/OpenHarmonyFormAdapter.ts
// OpenHarmony 表单平台适配
// ============================================

import { Platform, Keyboard } from 'react-native';
import type { UseFormOptions } from '../types/form';

/**
 * 平台类型
 */
export enum PlatformType {
  ANDROID = 'android',
  IOS = 'ios',
  OPENHARMONY = 'openharmony',
  WEB = 'web'
}

/**
 * 获取当前平台
 */
export function getPlatform(): PlatformType {
  const platform = Platform.OS;
  if (platform === 'harmony' || platform === 'ohos') {
    return PlatformType.OPENHARMONY;
  }
  return platform as PlatformType.ANDROID | PlatformType.IOS | PlatformType.WEB;
}

/**
 * 是否为 OpenHarmony 平台
 */
export function isOpenHarmony(): boolean {
  return getPlatform() === PlatformType.OPENHARMONY;
}

/**
 * OpenHarmony 优化的 useForm 配置
 */
export function getOpenHarmonyFormOptions<T>(
  baseOptions: UseFormOptions<T>
): UseFormOptions<T> {
  if (!isOpenHarmony()) {
    return baseOptions;
  }

  // OpenHarmony 平台优化配置
  return {
    ...baseOptions,
    // 使用更长的防抖延迟,因为 OpenHarmony 输入法响应较慢
    debounceDelay: baseOptions.debounceDelay ?? 500,
    // 推荐使用 onBlur 模式,减少频繁验证
    mode: baseOptions.mode ?? 'onBlur'
  };
}

/**
 * OpenHarmony 键盘行为差异对照表
 */
export const KeyboardBehaviorDifferences = {
  android: {
    dismissOnOutsidePress: true,
    animation: 'smooth',
    autoAdjust: true
  },
  ios: {
    dismissOnOutsidePress: true,
    animation: 'smooth',
    autoAdjust: true
  },
  openharmony: {
    dismissOnOutsidePress: false, // 需要手动处理
    animation: 'instant',
    autoAdjust: false // 需要手动处理布局调整
  }
} as const;

/**
 * OpenHarmony 焦点管理工具
 */
export class FocusManager {
  private static activeInputRef: any = null;

  /**
   * 注册当前活动输入框
   */
  static registerInput(ref: any) {
    this.activeInputRef = ref;
  }

  /**
   * 取消当前输入框焦点
   */
  static dismissActiveInput() {
    if (isOpenHarmony()) {
      // OpenHarmony 需要显式调用 blur
      this.activeInputRef?.blur?.();
    }
    Keyboard.dismiss();
    this.activeInputRef = null;
  }

  /**
   * 创建外部点击处理器
   */
  static createOutsidePressHandler() {
    if (!isOpenHarmony()) {
      return undefined; // Android/iOS 不需要
    }

    return () => {
      this.dismissActiveInput();
    };
  }
}

/**
 * 表单容器组件属性
 */
export interface FormContainerProps {
  children: React.ReactNode;
  style?: any;
  contentContainerStyle?: any;
}

/**
 * OpenHarmony 优化的表单容器
 */
export function FormContainer({
  children,
  style,
  contentContainerStyle
}: FormContainerProps) {
  const outsidePressHandler = FocusManager.createOutsidePressHandler();

  return (
    <View
      style={style}
      onStartShouldSetResponder={() => !!outsidePressHandler}
      onResponderRelease={outsidePressHandler}
    >
      {children}
    </View>
  );
}

六、 键盘管理工具

// ============================================
// utils/KeyboardManager.ts
// 键盘管理工具
// ============================================

import {
  Keyboard,
  Platform,
  KeyboardAvoidingView,
  ScrollView,
  View
} from 'react-native';
import { isOpenHarmony } from '../platform/OpenHarmonyFormAdapter';
import React, { useState, useEffect } from 'react';

/**
 * 键盘高度 Hook
 */
export function useKeyboardHeight() {
  const [keyboardHeight, setKeyboardHeight] = useState(0);

  useEffect(() => {
    const showSubscription = Keyboard.addListener('keyboardDidShow', (e) => {
      setKeyboardHeight(e.endCoordinates.height);
    });
    const hideSubscription = Keyboard.addListener('keyboardDidHide', () => {
      setKeyboardHeight(0);
    });

    return () => {
      showSubscription.remove();
      hideSubscription.remove();
    };
  }, []);

  return keyboardHeight;
}

/**
 * 键盘可见状态 Hook
 */
export function useKeyboardVisible() {
  const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);

  useEffect(() => {
    const showSubscription = Keyboard.addListener('keyboardDidShow', () => {
      setIsKeyboardVisible(true);
    });
    const hideSubscription = Keyboard.addListener('keyboardDidHide', () => {
      setIsKeyboardVisible(false);
    });

    return () => {
      showSubscription.remove();
      hideSubscription.remove();
    };
  }, []);

  return isKeyboardVisible;
}

/**
 * KeyboardAvoidingView 包装组件
 * OpenHarmony 平台使用特殊配置
 */
export function SmartKeyboardAvoidingView({
  children,
  style,
  contentContainerStyle
}: {
  children: React.ReactNode;
  style?: any;
  contentContainerStyle?: any;
}) {
  if (isOpenHarmony()) {
    // OpenHarmony 使用 padding 行为
    return (
      <KeyboardAvoidingView
        style={style}
        behavior="padding"
        keyboardVerticalOffset={0}
      >
        {children}
      </KeyboardAvoidingView>
    );
  }

  // iOS/Android 默认配置
  return (
    <KeyboardAvoidingView
      style={style}
      behavior={Platform.OS === 'ios' ? 'padding' : undefined}
    >
      {children}
    </KeyboardAvoidingView>
  );
}

/**
 * 带键盘避让的 ScrollView
 */
export function KeyboardAwareScrollView({
  children,
  style,
  contentContainerStyle
}: {
  children: React.ReactNode;
  style?: any;
  contentContainerStyle?: any;
}) {
  const keyboardHeight = useKeyboardHeight();

  return (
    <ScrollView
      style={style}
      contentContainerStyle={[
        contentContainerStyle,
        { paddingBottom: keyboardHeight }
      ]}
      keyboardShouldPersistTaps="handled"
    >
      {children}
    </ScrollView>
  );
}

/**
 * 输入框滚动视图管理器
 * 用于处理键盘遮挡输入框的问题
 */
export class InputScrollManager {
  private static scrollRef: any = null;
  private static inputPositionY: number = 0;

  /**
   * 注册滚动视图引用
   */
  static registerScroll(ref: any) {
    this.scrollRef = ref;
  }

  /**
   * 记录输入框位置
   */
  static recordInputPosition(y: number) {
    this.inputPositionY = y;
  }

  /**
   * 滚动到输入框位置
   */
  static scrollToInput(offset: number = 100) {
    if (!this.scrollRef) return;

    const scrollToY = Math.max(0, this.inputPositionY - offset);

    this.scrollRef.scrollTo({
      y: scrollToY,
      animated: true
    });
  }

  /**
   * 清理
   */
  static cleanup() {
    this.scrollRef = null;
    this.inputPositionY = 0;
  }
}

/**
 * 自动滚动输入框组件
 */
export function AutoScrollInput({
  children,
  scrollOffset = 100
}: {
  children: React.ReactElement;
  scrollOffset?: number;
}) {
  const isKeyboardVisible = useKeyboardVisible();

  useEffect(() => {
    if (isKeyboardVisible) {
      // 延迟滚动,等待键盘动画完成
      setTimeout(() => {
        InputScrollManager.scrollToInput(scrollOffset);
      }, isOpenHarmony() ? 100 : 300);
    }
  }, [isKeyboardVisible, scrollOffset]);

  return children;
}

七、完整使用示例

7.1 登录表单组件

// ============================================
// examples/LoginForm.tsx
// 登录表单示例
// ============================================

import React from 'react';
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  StyleSheet,
  ScrollView,
  ActivityIndicator,
  Alert
} from 'react-native';
import { useForm } from '../hooks/useForm';
import { Validators, CrossFieldValidator } from '../utils/validators';
import { FormContainer } from '../platform/OpenHarmonyFormAdapter';
import { SmartKeyboardAvoidingView } from '../utils/KeyboardManager';

// 登录表单数据类型
interface LoginFormData {
  username: string;
  password: string;
  rememberMe: boolean;
}

export const LoginForm: React.FC = () => {
  const form = useForm<LoginFormData>({
    initialValues: {
      username: '',
      password: '',
      rememberMe: false
    },
    validationRules: {
      username: Validator.combine(
        Validators.required('请输入用户名'),
        Validators.username(),
        Validator.minLength(3),
        Validator.maxLength(20)
      ),
      password: Validator.combine(
        Validators.required('请输入密码'),
        Validator.minLength(6),
        Validator.maxLength(20)
      )
    },
    mode: 'onBlur',
    debounceDelay: isOpenHarmony() ? 500 : 300
  });

  const handleSubmit = async (values: LoginFormData) => {
    try {
      // 模拟 API 调用
      await new Promise(resolve => setTimeout(resolve, 1500));
      Alert.alert('登录成功', `欢迎, ${values.username}!`);
    } catch (error) {
      Alert.alert('登录失败', '用户名或密码错误');
    }
  };

  return (
    <SmartKeyboardAvoidingView style={styles.container}>
      <FormContainer style={styles.content}>
        <ScrollView showsVerticalScrollIndicator={false}>
          {/* 标题 */}
          <Text style={styles.title}>欢迎回来</Text>
          <Text style={styles.subtitle}>登录以继续使用</Text>

          {/* 用户名输入 */}
          <View style={styles.inputGroup}>
            <Text style={styles.label}>用户名</Text>
            <TextInput
              style={[
                styles.input,
                form.errors.username && styles.inputError
              ]}
              value={form.values.username}
              onChangeText={form.handleChange('username')}
              onBlur={form.handleBlur('username')}
              placeholder="请输入用户名"
              placeholderTextColor="#999"
              autoCapitalize="none"
              autoCorrect={false}
            />
            {form.touched.username && form.errors.username && (
              <Text style={styles.error}>{form.errors.username}</Text>
            )}
          </View>

          {/* 密码输入 */}
          <View style={styles.inputGroup}>
            <Text style={styles.label}>密码</Text>
            <TextInput
              style={[
                styles.input,
                form.errors.password && styles.inputError
              ]}
              value={form.values.password}
              onChangeText={form.handleChange('password')}
              onBlur={form.handleBlur('password')}
              placeholder="请输入密码"
              placeholderTextColor="#999"
              secureTextEntry
              autoCapitalize="none"
              autoCorrect={false}
            />
            {form.touched.password && form.errors.password && (
              <Text style={styles.error}>{form.errors.password}</Text>
            )}
          </View>

          {/* 记住我 */}
          <TouchableOpacity
            style={styles.checkbox}
            onPress={() =>
              form.setValue('rememberMe', !form.values.rememberMe)
            }
          >
            <View
              style={[
                styles.checkboxBox,
                form.values.rememberMe && styles.checkboxChecked
              ]}
            />
            <Text style={styles.checkboxLabel}>记住我</Text>
          </TouchableOpacity>

          {/* 登录按钮 */}
          <TouchableOpacity
            style={[
              styles.submitButton,
              (!form.isValid || form.isSubmitting) && styles.submitButtonDisabled
            ]}
            onPress={form.handleSubmit(handleSubmit)}
            disabled={!form.isValid || form.isSubmitting}
          >
            {form.isSubmitting ? (
              <ActivityIndicator color="#fff" />
            ) : (
              <Text style={styles.submitButtonText}>登录</Text>
            )}
          </TouchableOpacity>

          {/* 表单状态 */}
          <View style={styles.formStatus}>
            <Text style={styles.statusText}>
              提交次数: {form.submitCount}
            </Text>
            <Text style={styles.statusText}>
              验证状态: {form.isValid ? '通过' : '失败'}
            </Text>
          </View>
        </ScrollView>
      </FormContainer>
    </SmartKeyboardAvoidingView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5'
  },
  content: {
    flex: 1,
    padding: 20
  },
  title: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 8
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
    marginBottom: 32
  },
  inputGroup: {
    marginBottom: 20
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    color: '#333',
    marginBottom: 8
  },
  input: {
    backgroundColor: '#fff',
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    paddingHorizontal: 16,
    paddingVertical: 14,
    fontSize: 16
  },
  inputError: {
    borderColor: '#ff3b30'
  },
  error: {
    color: '#ff3b30',
    fontSize: 12,
    marginTop: 6
  },
  checkbox: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 24
  },
  checkboxBox: {
    width: 20,
    height: 20,
    borderWidth: 2,
    borderColor: '#ddd',
    borderRadius: 4,
    marginRight: 8
  },
  checkboxChecked: {
    backgroundColor: '#34C759',
    borderColor: '#34C759'
  },
  checkboxLabel: {
    fontSize: 14,
    color: '#666'
  },
  submitButton: {
    backgroundColor: '#34C759',
    borderRadius: 12,
    paddingVertical: 16,
    alignItems: 'center'
  },
  submitButtonDisabled: {
    backgroundColor: '#ccc'
  },
  submitButtonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '700'
  },
  formStatus: {
    marginTop: 20,
    padding: 16,
    backgroundColor: '#f8f8f8',
    borderRadius: 8
  },
  statusText: {
    fontSize: 12,
    color: '#666',
    marginBottom: 4
  }
});

7.2 注册表单组件(带密码确认)

// ============================================
// examples/RegistrationForm.tsx
// 注册表单示例
// ============================================

import React from 'react';
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  StyleSheet,
  ScrollView
} from 'react-native';
import { useForm } from '../hooks/useForm';
import { Validators, CrossFieldValidator } from '../utils/validators';

interface RegistrationFormData {
  email: string;
  password: string;
  confirmPassword: string;
  agreeTerms: boolean;
}

export const RegistrationForm: React.FC = () => {
  const form = useForm<RegistrationFormData>({
    initialValues: {
      email: '',
      password: '',
      confirmPassword: '',
      agreeTerms: false
    },
    validationRules: {
      email: Validators.email(),
      password: Validator.combine(
        Validators.required('请输入密码'),
        Validators.passwordStrong()
      ),
      confirmPassword: CrossFieldValidator.match('password', '两次输入的密码不一致')
    },
    mode: 'onChangeBlur'
  });

  return (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>创建账户</Text>

      {/* 邮箱 */}
      <View style={styles.field}>
        <Text style={styles.label}>邮箱</Text>
        <TextInput
          style={styles.input}
          value={form.values.email}
          onChangeText={form.handleChange('email')}
          onBlur={form.handleBlur('email')}
          placeholder="your@email.com"
          keyboardType="email-address"
          autoCapitalize="none"
        />
        {form.touched.email && form.errors.email && (
          <Text style={styles.error}>{form.errors.email}</Text>
        )}
      </View>

      {/* 密码 */}
      <View style={styles.field}>
        <Text style={styles.label}>密码</Text>
        <TextInput
          style={styles.input}
          value={form.values.password}
          onChangeText={form.handleChange('password')}
          onBlur={form.handleBlur('password')}
          placeholder="至少8位,包含大小写字母和数字"
          secureTextEntry
        />
        {form.touched.password && form.errors.password && (
          <Text style={styles.error}>{form.errors.password}</Text>
        )}
      </View>

      {/* 确认密码 */}
      <View style={styles.field}>
        <Text style={styles.label}>确认密码</Text>
        <TextInput
          style={styles.input}
          value={form.values.confirmPassword}
          onChangeText={form.handleChange('confirmPassword')}
          onBlur={form.handleBlur('confirmPassword')}
          placeholder="再次输入密码"
          secureTextEntry
        />
        {form.touched.confirmPassword && form.errors.confirmPassword && (
          <Text style={styles.error}>{form.errors.confirmPassword}</Text>
        )}
      </View>

      {/* 提交按钮 */}
      <TouchableOpacity
        style={[
          styles.submitButton,
          (!form.isValid || !form.values.agreeTerms) && styles.submitButtonDisabled
        ]}
        onPress={form.handleSubmit(async (values) => {
          // 提交逻辑
          console.log('注册:', values);
        })}
      >
        <Text style={styles.submitButtonText}>注册</Text>
      </TouchableOpacity>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#fff'
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    marginBottom: 24
  },
  field: {
    marginBottom: 20
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    marginBottom: 8
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    paddingHorizontal: 16,
    paddingVertical: 12
  },
  error: {
    color: '#ff3b30',
    fontSize: 12,
    marginTop: 4
  },
  submitButton: {
    backgroundColor: '#34C759',
    paddingVertical: 16,
    borderRadius: 12,
    alignItems: 'center',
    marginTop: 8
  },
  submitButtonDisabled: {
    backgroundColor: '#ccc'
  },
  submitButtonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '700'
  }
});

八、最佳实践总结

8.1 表单设计与管理的最佳实践

┌─────────────────────────────────────────────────────────────────┐
│                      useForm 最佳实践架构                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌────────────────────┐      ┌────────────────────┐            │
│  │   数据层           │      │   验证层           │            │
│  │                    │      │                    │            │
│  │ • initialValues    │      │ • validationRules  │            │
│  │ • 状态管理         │      │ • 同步验证         │            │
│  │ • 脏数据检测       │      │ • 异步验证         │            │
│  └────────────────────┘      └────────────────────┘            │
│            │                          │                        │
│            └──────────┬───────────────┘                        │
│                       ▼                                        │
│            ┌────────────────────┐                              │
│            │   useForm Hook     │                              │
│            │                    │                              │
│            │ • 状态追踪         │                              │
│            │ • 防抖处理         │                              │
│            │ • 提交管理         │                              │
│            └────────────────────┘                              │
│                       │                                        │
│                       ▼                                        │
│  ┌──────────────────────────────────────────────────────┐    │
│  │                    UI 组件层                           │    │
│  │                                                       │    │
│  │  • TextInput  • CheckBox  • Select  • DatePicker     │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

8.2 OpenHarmony 适配检查清单
表格形式整理

项目 检查点 说明
焦点管理 外部点击失焦 使用 FocusManager 处理
键盘处理 键盘避让 使用 SmartKeyboardAvoidingView
输入验证 防抖延迟 设置 500ms 防抖
滚动定位 输入框可见 使用 InputScrollManager
提交优化 超时重试 实现指数退避机制
状态持久化 暂停保存 保存表单到持久化存储

8.3 性能优化建议
状态更新优化

  • 使用 useCallback 缓存回调函数,避免不必要的重新创建
  • 采用防抖机制减少频繁的验证触发
  • 支持批量更新多个字段值,减少渲染次数
    验证优化
  • 实现按需验证策略(如 onBluronChange 事件触发)
  • 预编译正则表达式提升验证效率
  • 对耗时验证任务使用 Web Worker 进行异步处理
    渲染优化
  • 使用 React.memo 包装表单字段组件
  • 避免在渲染过程中动态创建对象或函数
  • 利用 useRef 存储与渲染无关的数据

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

Logo

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

更多推荐