【HarmonyOS实战】OpenHarmony + RN:自定义 useFormik 表单处理

OpenHarmony + RN:自定义 useFormik 表单处理


🌸你好呀!我是 lbb小魔仙
🌟 感谢陪伴~ 小白博主在线求友
🌿 跟着小白学Linux/Java/Python
📖 专栏汇总:
《Linux》专栏 | 《Java》专栏 | 《Python》专栏

  • [OpenHarmony + RN:自定义 useFormik 表单处理](#OpenHarmony + RN:自定义 useFormik 表单处理)
    • 摘要
    • 目录
    • [1. Formik 表单框架介绍](#1. Formik 表单框架介绍)
      • [1.1 核心架构](#1.1 核心架构)
      • [1.2 OpenHarmony 特殊挑战](#1.2 OpenHarmony 特殊挑战)
    • [2. TypeScript 类型系统设计](#2. TypeScript 类型系统设计)
    • [3. 核心 useFormik Hook 实现](#3. 核心 useFormik Hook 实现)
    • [4. 表单验证架构](#4. 表单验证架构)
    • [5. OpenHarmony 平台适配](#5. OpenHarmony 平台适配)
    • [6. 后台任务管理](#6. 后台任务管理)
    • [7. 无障碍支持](#7. 无障碍支持)
    • [8. 完整使用示例](#8. 完整使用示例)
      • [8.1 登录表单](#8.1 登录表单)
    • [9. 最佳实践总结](#9. 最佳实践总结)
      • [9.1 useFormik 与 useForm 对比](#9.1 useFormik 与 useForm 对比)
      • [9.2 OpenHarmony 适配检查清单](#9.2 OpenHarmony 适配检查清单)
      • [9.3 性能优化建议](#9.3 性能优化建议)
    • 参考资料

摘要

本文深入探讨在 OpenHarmony 6.0.0 平台上使用 React Native 0.72.5 实现高性能表单处理方案。我们将重点解析 Formik 库的核心原理,结合 OpenHarmony 6.0.0 (API 20) 的表单交互特性,通过自定义 useFormik Hook 实现跨平台表单管理。


目录

  1. [Formik 表单框架介绍](#Formik 表单框架介绍)
  2. [TypeScript 类型系统设计](#TypeScript 类型系统设计)
  3. [核心 useFormik Hook 实现](#核心 useFormik Hook 实现)
  4. 表单验证架构
  5. [OpenHarmony 平台适配](#OpenHarmony 平台适配)
  6. 后台任务管理
  7. 无障碍支持
  8. 完整使用示例
  9. 最佳实践总结

1. Formik 表单框架介绍

Formik 是 React 生态中最流行的表单状态管理库之一,它通过提供可预测的表单状态管理和验证机制,显著简化了复杂表单的开发流程。

1.1 核心架构

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                       Formik 核心架构                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────┐      ┌──────────────┐      ┌──────────────┐  │
│  │ FormikBag    │      │ Validation   │      │   Field      │  │
│  │              │      │   Schema     │      │ Components   │  │
│  │ • values     │      │              │      │              │  │
│  │ • errors     │      │ • Yup        │      │ • Field       │  │
│  │ • touched    │      │ • Zod        │      │ • Formik      │  │
│  │ • dirty      │      │ • Custom     │      │ • ErrorMessage│  │
│  │ • status     │      │              │      │              │  │
│  └──────────────┘      └──────────────┘      └──────────────┘  │
│         │                     │                     │           │
│         └─────────────────────┴─────────────────────┘           │
│                               │                                 │
│                               ▼                                 │
│                    ┌──────────────────┐                         │
│                    │  useFormik Hook │                         │
│                    │                  │                         │
│                    │ • 状态管理        │                         │
│                    │ • 验证调度        │                         │
│                    │ • 提交处理        │                         │
│                    └──────────────────┘                         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

1.2 OpenHarmony 特殊挑战

特性 Android/iOS OpenHarmony 6.0.0 适配方案
输入法切换 系统自动处理 需监听键盘类型变化 使用 Keyboard 模块事件
表单提交 标准 HTTP 请求 需考虑后台任务限制 使用 TaskPool 优化
错误提示 Toast 自由定制 Toast 时长固定 2 秒 自定义提示组件
输入验证 即时验证 需考虑性能开销 防抖策略优化

2. TypeScript 类型系统设计

typescript 复制代码
// ============================================
// types/formik.ts
// Formik 表单类型定义
// ============================================

/**
 * 表单状态接口
 */
export interface FormikState<Values = Record<string, any>> {
  /** 表单值 */
  values: Values;
  /** 错误信息 */
  errors: Record<keyof Values, string | undefined>;
  /** 触碰状态 */
  touched: Record<keyof Values, boolean>;
  /** 是否已修改(脏数据) */
  dirty: boolean;
  /** 是否有效 */
  isValid: boolean;
  /** 是否正在提交 */
  isSubmitting: boolean;
  /** 提交次数 */
  submitCount: number;
  /** 正在验证的字段 */
  validatingFields: Set<keyof Values>;
}

/**
 * 表单操作接口
 */
export interface FormikActions<Values = Record<string, any>> {
  /** 设置字段值 */
  setFieldValue: <K extends keyof Values>(field: K, value: Values[K]) => void;
  /** 批量设置字段值 */
  setValues: (values: Values) => void;
  /** 设置错误信息 */
  setFieldError: <K extends keyof Values>(field: K, error: string) => void;
  /** 批量设置错误信息 */
  setErrors: (errors: Record<keyof Values, string>) => void;
  /** 设置触碰状态 */
  setFieldTouched: <K extends keyof Values>(field: K, touched: boolean) => void;
  /** 批量设置触碰状态 */
  setTouched: (touched: Record<keyof Values, boolean>) => void;
  /** 设置提交状态 */
  setSubmitting: (isSubmitting: boolean) => void;
  /** 设置状态 */
  setStatus: (status: any) => void;
  /** 重置表单 */
  resetForm: (nextValues?: Values) => void;
  /** 验证表单 */
  validateForm: () => Promise<Record<keyof Values, string>>;
  /** 验证单个字段 */
  validateField: <K extends keyof Values>(field: K) => Promise<string | undefined>;
}

/**
 * 验证函数类型
 */
export type FormikValidate<Values> = (
  values: Values
) => Record<keyof Values, string> | Promise<Record<keyof Values, string>> | void;

/**
 * 提交函数类型
 */
export type FormikSubmitHandler<Values> = (
  values: Values,
  formikHelpers: FormikHelpers<Values>
) => void | Promise<any>;

/**
 * Formik 辅助函数接口
 */
export interface FormikHelpers<Values = Record<string, any>> {
  /** 设置字段值 */
  setFieldValue: <K extends keyof Values>(field: K, value: Values[K]) => void;
  /** 设置错误信息 */
  setFieldError: <K extends keyof Values>(field: K, error: string) => void;
  /** 设置触碰状态 */
  setFieldTouched: <K extends keyof Values>(field: K, touched: boolean) => void;
  /** 设置提交状态 */
  setSubmitting: (isSubmitting: boolean) => void;
  /** 重置表单 */
  resetForm: () => void;
}

/**
 * 字段属性接口
 */
export interface FieldProps<Values = Record<string, any>, Field = keyof Values> {
  /** 字段名 */
  name: Field;
  /** 字段值 */
  value: Values[Field];
  /** 值变化回调 */
  onChange: (value: Values[Field]) => void;
  /** 失焦回调 */
  onBlur: () => void;
  /** 是否有错误 */
  hasError: boolean;
  /** 错误消息 */
  error: string | undefined;
}

/**
 * useFormik 配置选项
 */
export interface UseFormikOptions<Values = Record<string, any>> {
  /** 初始值 */
  initialValues: Values;
  /** 验证函数 */
  validate?: FormikValidate<Values>;
  /** 提交函数 */
  onSubmit: FormikSubmitHandler<Values>;
  /** 验证触发时机 */
  validateOnMount?: boolean;
  /** 变化时验证 */
  validateOnChange?: boolean;
  /** 失焦时验证 */
  validateOnBlur?: boolean;
  /** 防抖延迟(毫秒) */
  debounceDelay?: number;
  /** 是否启用脏数据追踪 */
  dirtyCheck?: boolean;
}

/**
 * Formik 返回值接口
 */
export interface UseFormikReturn<Values = Record<string, any>>
  extends FormikState<Values>,
    FormikActions<Values> {
  /** 字段变化处理 */
  handleChange: <K extends keyof Values>(field: K) => (value: Values[K]) => void;
  /** 字段失焦处理 */
  handleBlur: <K extends keyof Values>(field: K) => () => void;
  /** 提交处理 */
  handleSubmit: () => Promise<void>;
  /** 重置表单 */
  handleReset: () => void;
  /** 获取字段属性 */
  getFieldProps: <K extends keyof Values>(field: K) => FieldProps<Values, K>;
}

/**
 * 验证规则接口
 */
export interface ValidationRule<T = any> {
  /** 验证函数 */
  validate: (value: T) => string | undefined | Promise<string | undefined>;
  /** 验证类型 */
  type?: 'sync' | 'async';
  /** 优先级 */
  priority?: number;
}

/**
 * 验证 Schema 接口
 */
export type ValidationSchema<T = any> = Record<
  keyof T,
  ValidationRule<T[keyof T]> | ValidationRule<T[keyof T]>[]
>;

3. 核心 useFormik Hook 实现

typescript 复制代码
// ============================================
// hooks/useFormik.ts
// Formik 风格表单管理 Hook
// ============================================

import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import type {
  UseFormikOptions,
  UseFormikReturn,
  FormikState,
  FormikActions,
  FormikHelpers,
  FieldProps,
  FormikValidate,
  FormikSubmitHandler
} from '../types/formik';
import { ValidationEngine } from '../utils/ValidationEngine';

/**
 * Formik 风格表单管理 Hook
 * @param options 表单配置选项
 * @returns 表单状态和方法
 */
export function useFormik<Values extends Record<string, any>>(
  options: UseFormikOptions<Values>
): UseFormikReturn<Values> {
  const {
    initialValues,
    validate,
    onSubmit,
    validateOnMount = false,
    validateOnChange = false,
    validateOnBlur = true,
    debounceDelay = 300,
    dirtyCheck = true
  } = options;

  // 初始值引用
  const initialValuesRef = useRef(initialValues);

  // 防抖定时器
  const debounceTimersRef = useRef<Map<keyof Values, ReturnType<typeof setTimeout>>>(
    new Map()
  );

  // 表单状态
  const [state, setState] = useState<FormikState<Values>>({
    values: { ...initialValues },
    errors: {},
    touched: {},
    dirty: false,
    isValid: true,
    isSubmitting: false,
    submitCount: 0,
    validatingFields: new Set()
  });

  // 验证引擎
  const validationEngine = useMemo(
    () => new ValidationEngine<Values>(validate, debounceDelay),
    [validate, debounceDelay]
  );

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

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

      // 根据模式决定是否验证
      if (validateOnChange) {
        validationEngine.validateField(state.values, field);
      }
    },
    [validateOnChange, validationEngine, state.values, dirtyCheck]
  );

  /**
   * 批量设置字段值
   */
  const setValues = useCallback((values: Values) => {
    setState(prev => {
      const newValues = { ...values };
      const dirty = dirtyCheck
        ? JSON.stringify(newValues) !== JSON.stringify(initialValuesRef.current)
        : prev.dirty;

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

  /**
   * 设置错误信息
   */
  const setFieldError = useCallback(<K extends keyof Values>(field: K, error: string) => {
    setState(prev => ({
      ...prev,
      errors: { ...prev.errors, [field]: error },
      isValid: !error && Object.values(prev.errors).every(e => !e)
    }));
  }, []);

  /**
   * 批量设置错误信息
   */
  const setErrors = useCallback((errors: Record<keyof Values, string>) => {
    setState(prev => ({
      ...prev,
      errors,
      isValid: Object.values(errors).every(e => !e)
    }));
  }, []);

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

    // 根据模式决定是否验证
    if (validateOnBlur) {
      validationEngine.validateField(state.values, field);
    }
  }, [validateOnBlur, validationEngine, state.values]);

  /**
   * 批量设置触碰状态
   */
  const setTouched = useCallback((touched: Record<keyof Values, boolean>) => {
    setState(prev => ({
      ...prev,
      touched
    }));
  }, []);

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

  /**
   * 设置状态
   */
  const setStatus = useCallback((status: any) => {
    setState(prev => ({
      ...prev,
      status
    }));
  }, []);

  /**
   * 重置表单
   */
  const resetForm = useCallback((nextValues?: Values) => {
    // 清除所有定时器
    debounceTimersRef.current.forEach(timer => clearTimeout(timer));
    debounceTimersRef.current.clear();

    const values = nextValues || { ...initialValuesRef.current };

    setState({
      values,
      errors: {},
      touched: {},
      dirty: false,
      isValid: true,
      isSubmitting: false,
      submitCount: 0,
      validatingFields: new Set()
    });
  }, []);

  /**
   * 验证表单
   */
  const validateForm = useCallback(async (): Promise<Record<keyof Values, string>> => {
    if (!validate) return {};

    const errors = await validate(state.values);

    setState(prev => ({
      ...prev,
      errors: errors || {},
      isValid: !Object.values(errors || {}).some(e => e)
    }));

    return errors || {};
  }, [validate, state.values]);

  /**
   * 验证单个字段
   */
  const validateField = useCallback(
    async <K extends keyof Values>(field: K): Promise<string | undefined> => {
      const error = await validationEngine.validateField(state.values, field);

      setState(prev => ({
        ...prev,
        errors: { ...prev.errors, [field]: error },
        isValid: !error && Object.values(prev.errors).every((e, i) =>
          i === Object.keys(prev.errors).indexOf(String(field)) ? !error : !e
        )
      }));

      return error;
    },
    [validationEngine, state.values]
  );

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

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

  /**
   * 提交处理
   */
  const handleSubmit = useCallback(async () => {
    // 标记所有字段为已触碰
    const touched = Object.keys(state.values).reduce(
      (acc, key) => ({ ...acc, [key]: true }),
      {} as Record<keyof Values, boolean>
    );
    setTouched(touched);

    // 验证表单
    const errors = await validateForm();

    if (Object.values(errors).some(e => e)) {
      setState(prev => ({
        ...prev,
        submitCount: prev.submitCount + 1
      }));
      return;
    }

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

    try {
      const helpers: FormikHelpers<Values> = {
        setFieldValue,
        setFieldError,
        setFieldTouched,
        setSubmitting,
        resetForm
      };

      await onSubmit(state.values, helpers);
    } finally {
      setSubmitting(false);
    }
  }, [state.values, validateForm, setTouched, setSubmitting, onSubmit, setFieldValue, setFieldError, setFieldTouched, resetForm]);

  /**
   * 重置处理
   */
  const handleReset = useCallback(() => {
    resetForm();
  }, [resetForm]);

  /**
   * 获取字段属性
   */
  const getFieldProps = useCallback(
    <K extends keyof Values>(field: K): FieldProps<Values, K> => {
      return {
        name: field,
        value: state.values[field],
        onChange: handleChange(field),
        onBlur: handleBlur(field),
        hasError: !!state.errors[field] && !!state.touched[field],
        error: state.errors[field]
      };
    },
    [state.values, state.errors, state.touched, handleChange, handleBlur]
  );

  // 初始挂载验证
  useEffect(() => {
    if (validateOnMount) {
      validateForm();
    }
  }, []);

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

  return {
    ...state,
    setFieldValue,
    setValues,
    setFieldError,
    setErrors,
    setFieldTouched,
    setTouched,
    setSubmitting,
    setStatus,
    resetForm,
    validateForm,
    validateField,
    handleChange,
    handleBlur,
    handleSubmit,
    handleReset,
    getFieldProps
  };
}

/**
 * 默认导出
 */
export default useFormik;

4. 表单验证架构

typescript 复制代码
// ============================================
// utils/ValidationEngine.ts
// 表单验证引擎
// ============================================

import type { FormikValidate } from '../types/formik';

/**
 * 验证引擎类
 * 负责管理表单验证的调度和执行
 */
export class ValidationEngine<Values = Record<string, any>> {
  private validateFn?: FormikValidate<Values>;
  private debounceDelay: number;
  private timers: Map<keyof Values, ReturnType<typeof setTimeout>>;
  private cache: Map<keyof Values, string | undefined>;

  constructor(validateFn?: FormikValidate<Values>, debounceDelay: number = 300) {
    this.validateFn = validateFn;
    this.debounceDelay = debounceDelay;
    this.timers = new Map();
    this.cache = new Map();
  }

  /**
   * 验证单个字段
   * @param values 表单值
   * @param field 字段名
   * @returns 错误信息
   */
  async validateField(
    values: Values,
    field: keyof Values
  ): Promise<string | undefined> {
    // 清除之前的定时器
    const timer = this.timers.get(field);
    if (timer) {
      clearTimeout(timer);
      this.timers.delete(field);
    }

    // 检查缓存
    if (this.cache.has(field)) {
      return this.cache.get(field);
    }

    // 返回 Promise 用于防抖验证
    return new Promise(resolve => {
      this.timers.set(
        field,
        setTimeout(async () => {
          let error: string | undefined;

          if (this.validateFn) {
            const errors = await this.validateFn(values);
            error = errors?.[field];
          }

          // 更新缓存
          this.cache.set(field, error);
          this.timers.delete(field);

          resolve(error);
        }, this.debounceDelay)
      );
    });
  }

  /**
   * 验证所有字段
   * @param values 表单值
   * @returns 错误对象
   */
  async validateAll(values: Values): Promise<Record<string, string>> {
    // 清除所有定时器
    this.timers.forEach(timer => clearTimeout(timer));
    this.timers.clear();

    if (!this.validateFn) {
      return {};
    }

    const errors = await this.validateFn(values);

    // 更新缓存
    Object.entries(errors || {}).forEach(([field, error]) => {
      this.cache.set(field as keyof Values, error);
    });

    return errors || {};
  }

  /**
   * 清除缓存
   */
  clearCache(field?: keyof Values): void {
    if (field) {
      this.cache.delete(field);
    } else {
      this.cache.clear();
    }
  }

  /**
   * 清理资源
   */
  dispose(): void {
    this.timers.forEach(timer => clearTimeout(timer));
    this.timers.clear();
    this.cache.clear();
  }
}

/**
 * 预设验证规则
 */
export const ValidationRules = {
  /** 必填验证 */
  required: (message: string = '此字段为必填项') => (value: any) => {
    if (value === null || value === undefined) return message;
    if (typeof value === 'string' && value.trim() === '') return message;
    if (Array.isArray(value) && value.length === 0) return message;
    return undefined;
  },

  /** 最小长度验证 */
  minLength: (min: number, message?: string) => (value: string) => {
    const len = value?.length ?? 0;
    return len < min ? (message ?? `长度不能少于 ${min} 个字符`) : undefined;
  },

  /** 最大长度验证 */
  maxLength: (max: number, message?: string) => (value: string) => {
    const len = value?.length ?? 0;
    return len > max ? (message ?? `长度不能超过 ${max} 个字符`) : undefined;
  },

  /** 邮箱验证 */
  email: (message: string = '请输入有效的邮箱地址') => (value: string) => {
    const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return value && !pattern.test(value) ? message : undefined;
  },

  /** 手机号验证 */
  phone: (message: string = '请输入有效的手机号码') => (value: string) => {
    const pattern = /^1[3-9]\d{9}$/;
    return value && !pattern.test(value) ? message : undefined;
  },

  /** 正则验证 */
  pattern: (regex: RegExp, message: string = '格式不正确') => (value: string) => {
    return value && !regex.test(value) ? message : undefined;
  },

  /** 自定义验证 */
  custom: <T>(
    validator: (value: T) => string | undefined,
    message?: string
  ) => (value: T) => {
    const result = validator(value);
    return result ? (message || result) : undefined;
  }
} as const;

/**
 * 组合多个验证规则
 */
export function compose<T = any>(
  ...rules: Array<(value: T) => string | undefined>
): (value: T) => string | undefined {
  return (value: T) => {
    for (const rule of rules) {
      const error = rule(value);
      if (error) return error;
    }
    return undefined;
  };
}

5. OpenHarmony 平台适配

typescript 复制代码
// ============================================
// platform/FormikPlatformAdapter.ts
// Formik OpenHarmony 平台适配
// ============================================

import { Platform, Keyboard, TextInput } from 'react-native';
import type { FieldProps } from '../types/formik';

/**
 * 平台类型
 */
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 表单组件属性转换器
 */
export class FormikPropsAdapter {
  /**
   * 转换 TextInput 属性
   */
  static transformTextInputProps<T = any>(
    props: FieldProps<T>
  ): Omit<FieldProps<T>, 'hasError' | 'error'> & {
    onChangeText: (text: string) => void;
    onBlur?: () => void;
  } {
    const { onChange, onBlur, value, hasError, error, ...rest } = props;

    return {
      ...rest,
      value: value as any,
      // OpenHarmony 使用 onChangeText 而非 onChange
      onChangeText: (text: string) => onChange(text as any),
      // OpenHarmony 的 onBlur 事件需要特殊处理
      onBlur: () => {
        onBlur?.();
        // OpenHarmony 需要延迟处理失焦事件
        if (isOpenHarmony()) {
          setTimeout(() => {
            Keyboard.dismiss();
          }, 100);
        }
      }
    };
  }

  /**
   * 转换 Switch 属性
   */
  static transformSwitchProps<T = any>(
    props: FieldProps<T>
  ): {
    value: boolean;
    onValueChange: (value: boolean) => void;
  } {
    const { onChange, value } = props;

    return {
      value: value as unknown as boolean,
      // OpenHarmony 使用 onValueChange 而非 onChange
      onValueChange: (newValue: boolean) => onChange(newValue as any)
    };
  }
}

/**
 * OpenHarmony 键盘管理器
 */
export class OpenHarmonyKeyboardManager {
  private static listeners: Set<() => void> = new Set();
  private static currentHeight: number = 0;

  /**
   * 初始化键盘监听
   */
  static initialize() {
    if (!isOpenHarmony()) return;

    Keyboard.addListener('keyboardDidShow', (e) => {
      this.currentHeight = e.endCoordinates.height;
      this.notifyListeners();
    });

    Keyboard.addListener('keyboardDidHide', () => {
      this.currentHeight = 0;
      this.notifyListeners();
    });
  }

  /**
   * 订阅键盘变化
   */
  static subscribe(callback: () => void): () => void {
    this.listeners.add(callback);
    return () => {
      this.listeners.delete(callback);
    };
  }

  /**
   * 通知订阅者
   */
  private static notifyListeners() {
    this.listeners.forEach(listener => listener());
  }

  /**
   * 获取当前键盘高度
   */
  static getKeyboardHeight(): number {
    return this.currentHeight;
  }

  /**
   * 隐藏键盘
   */
  static dismiss() {
    Keyboard.dismiss();
  }
}

/**
 * 表单字段包装器组件属性
 */
export interface FormikFieldProps<T = any> {
  name: string;
  component: React.ComponentType<any>;
  validate?: (value: T) => string | undefined;
  [key: string]: any;
}

/**
 * Formik 字段包装器
 * 用于自动处理表单字段绑定
 */
export function FormikField<T = any>({
  name,
  component: Component,
  ...rest
}: FormikFieldProps<T>) {
  // 通过 Context 获取 Formik 状态
  // 这里简化处理,实际应使用 Formik Context

  return (
    <Component
      name={name}
      {...rest}
    />
  );
}

6. 后台任务管理

typescript 复制代码
// ============================================
// platform/BackgroundTaskManager.ts
// OpenHarmony 后台任务管理
// ============================================

/**
 * 任务类型
 */
export enum TaskType {
  /** 短任务(< 3 分钟) */
  SHORT = 'short',
  /** 长任务(3-10 分钟) */
  LONG = 'long',
  /** 超长任务(> 10 分钟) */
  EXTENDED = 'extended'
}

/**
 * 任务配置
 */
export interface TaskConfig {
  /** 任务类型 */
  type: TaskType;
  /** 超时时间(毫秒) */
  timeout: number;
  /** 是否使用 WorkScheduler */
  useWorkScheduler?: boolean;
}

/**
 * 后台任务管理器
 */
export class BackgroundTaskManager {
  private static tasks: Map<string, TaskConfig> = new Map();

  /**
   * 注册后台任务
   * @param taskId 任务 ID
   * @param config 任务配置
   */
  static registerTask(taskId: string, config: TaskConfig): void {
    if (!isOpenHarmony()) {
      console.warn('BackgroundTaskManager is only for OpenHarmony');
      return;
    }

    this.tasks.set(taskId, config);
  }

  /**
   * 执行后台任务
   * @param taskId 任务 ID
   * @param task 任务函数
   */
  static async executeTask<T>(
    taskId: string,
    task: () => Promise<T>
  ): Promise<T> {
    const config = this.tasks.get(taskId);

    if (!config) {
      console.warn(`Task ${taskId} not registered`);
      return task();
    }

    // 根据任务类型处理
    switch (config.type) {
      case TaskType.SHORT:
        // 短任务直接执行
        return await Promise.race([
          task(),
          new Promise<never>((_, reject) =>
            setTimeout(() => reject(new Error('Task timeout')), config.timeout)
          )
        ]);

      case TaskType.LONG:
        // 长任务分块处理
        return await this.executeLongTask(task, config.timeout);

      case TaskType.EXTENDED:
        // 超长任务使用 WorkScheduler
        if (config.useWorkScheduler) {
          return await this.executeWithWorkScheduler(taskId, task);
        }
        return await task();

      default:
        return await task();
    }
  }

  /**
   * 执行长任务(分块处理)
   */
  private static async executeLongTask<T>(
    task: () => Promise<T>,
    timeout: number
  ): Promise<T> {
    const chunkSize = Math.floor(timeout / 3);
    const startTime = Date.now();

    return task().finally(() => {
      const elapsed = Date.now() - startTime;
      if (elapsed > timeout) {
        console.warn(`Long task exceeded timeout: ${elapsed}ms`);
      }
    });
  }

  /**
   * 使用 WorkScheduler 执行任务
   */
  private static async executeWithWorkScheduler<T>(
    taskId: string,
    task: () => Promise<T>
  ): Promise<T> {
    // 调用 OpenHarmony 的 WorkScheduler API
    // 这里简化处理
    console.log(`Executing task ${taskId} with WorkScheduler`);
    return await task();
  }

  /**
   * 取消任务
   */
  static cancelTask(taskId: string): void {
    this.tasks.delete(taskId);
  }

  /**
   * 获取任务配置
   */
  static getTaskConfig(taskId: string): TaskConfig | undefined {
    return this.tasks.get(taskId);
  }
}

/**
 * 表单提交包装器
 * 用于在 OpenHarmony 上处理表单提交
 */
export function useFormikSubmit<T>(
  submitFn: (values: T) => Promise<void>,
  taskId?: string
): (values: T) => Promise<void> {
  return async (values: T) => {
    if (isOpenHarmony() && taskId) {
      // 注册为短任务
      BackgroundTaskManager.registerTask(taskId, {
        type: TaskType.SHORT,
        timeout: 3 * 60 * 1000 // 3 分钟
      });

      return await BackgroundTaskManager.executeTask(taskId, () => submitFn(values));
    }

    return await submitFn(values);
  };
}

7. 无障碍支持

typescript 复制代码
// ============================================
// accessibility/FormFieldAccessibility.ts
// 表单字段无障碍支持
// ============================================

import type { FieldProps } from '../types/formik';

/**
 * 无障碍属性接口
 */
export interface AccessibilityProps {
  /** 无障碍标签 */
  accessibilityLabel?: string;
  /** 无障碍提示 */
  accessibilityHint?: string;
  /** 无障碍角色 */
  accessibilityRole?: 'none' | 'text' | 'search' | 'button';
  /** 无障碍状态 */
  accessibilityState?: {
    disabled?: boolean;
    selected?: boolean;
    checked?: boolean;
    busy?: boolean;
    expanded?: boolean;
  };
  /** 无障碍实时区域 */
  accessibilityLiveRegion?: 'none' | 'polite' | 'assertive';
  /** 焦点顺序 */
  accessibilityFlow?: string[];
  /** 转义手势处理 */
  onAccessibilityEscape?: () => void;
}

/**
 * 创建无障碍字段属性
 */
export function createAccessibleFieldProps<T>(
  fieldProps: FieldProps<T>,
  options: {
    label?: string;
    hint?: string;
    role?: AccessibilityProps['accessibilityRole'];
  } = {}
): AccessibilityProps {
  const { label, hint, role } = options;

  return {
    accessibilityLabel: label || String(fieldProps.name),
    accessibilityHint: hint || `${fieldProps.error ? '包含错误' : '输入字段'}`,
    accessibilityRole: role || 'text',
    accessibilityState: {
      disabled: false,
      selected: false,
      checked: false,
      busy: false,
      expanded: false
    },
    accessibilityLiveRegion: fieldProps.error ? 'assertive' : 'polite',
    // 错误状态需要实时通知
    ...(fieldProps.error && {
      onAccessibilityEscape: () => {
        // 清除错误
        console.log('Accessibility escape triggered');
      }
    })
  };
}

/**
 * 无障碍字段验证状态通知器
 */
export class AccessibilityNotifier {
  private static announcer?: any;

  /**
   * 初始化通知器
   */
  static initialize() {
    if (isOpenHarmony()) {
      // 初始化 OpenHarmony 的无障碍通知
      // 这里简化处理
    }
  }

  /**
   * 通知字段验证状态变化
   * @param fieldName 字段名
   * @param isValid 是否有效
   * @param error 错误信息
   */
  static announceValidationStatus(
    fieldName: string,
    isValid: boolean,
    error?: string
  ): void {
    const message = isValid
      ? `${fieldName} 验证通过`
      : `${fieldName} 验证失败: ${error}`;

    // 在 OpenHarmony 上使用无障碍 API 发送通知
    if (isOpenHarmony()) {
      console.log('[Accessibility]', message);
      // 调用 OpenHarmony 的 AccessibilityManager 发送通知
    }
  }

  /**
   * 通知表单提交状态
   * @param isSubmitting 是否正在提交
   * @param success 是否提交成功
   */
  static announceSubmitStatus(isSubmitting: boolean, success?: boolean): void {
    if (isSubmitting) {
      this.announce('正在提交表单,请稍候');
    } else if (success === true) {
      this.announce('表单提交成功');
    } else if (success === false) {
      this.announce('表单提交失败,请检查输入');
    }
  }

  /**
   * 发送无障碍通知
   * @param message 通知消息
   * @param priority 优先级
   */
  private static announce(message: string, priority: 'polite' | 'assertive' = 'polite'): void {
    console.log(`[Accessibility ${priority}]`, message);
    // 在实际实现中调用平台无障碍 API
  }
}

/**
 * 表单字段无障碍包装器组件属性
 */
export interface AccessibleFieldProps<T> extends FieldProps<T> {
  accessibilityLabel?: string;
  accessibilityHint?: string;
  onAccessibilityTap?: () => void;
}

/**
 * 表单字段无障碍包装器
 * 自动处理无障碍属性和通知
 */
export function AccessibleFormField<T>({
  accessibilityLabel,
  accessibilityHint,
  onAccessibilityTap,
  ...fieldProps
}: AccessibleFieldProps<T>) {
  // 监听错误状态变化并发送无障碍通知
  useEffect(() => {
    if (fieldProps.error && fieldProps.hasError) {
      AccessibilityNotifier.announceValidationStatus(
        String(fieldProps.name),
        false,
        fieldProps.error
      );
    }
  }, [fieldProps.error, fieldProps.hasError, fieldProps.name]);

  return {
    ...fieldProps,
    accessibilityLabel: accessibilityLabel || String(fieldProps.name),
    accessibilityHint: accessibilityHint || '请输入内容',
    accessibilityState: {
      disabled: false,
      invalid: fieldProps.hasError
    },
    accessibilityLiveRegion: fieldProps.hasError ? 'assertive' : 'polite'
  };
}

8. 完整使用示例

8.1 登录表单

typescript 复制代码
// ============================================
// examples/FormikLoginForm.tsx
// Formik 登录表单示例
// ============================================

import React from 'react';
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  StyleSheet,
  ScrollView,
  ActivityIndicator
} from 'react-native';
import useFormik, { ValidationRules, compose } from '../hooks/useFormik';
import { useFormikSubmit } from '../platform/BackgroundTaskManager';
import { OpenHarmonyKeyboardManager } from '../platform/FormikPlatformAdapter';

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

export const FormikLoginForm: React.FC = () => {
  // 初始化键盘管理器
  React.useEffect(() => {
    if (isOpenHarmony()) {
      OpenHarmonyKeyboardManager.initialize();
    }
  }, []);

  const formik = useFormik<LoginValues>({
    initialValues: {
      username: '',
      password: '',
      rememberMe: false
    },
    validate: (values) => {
      const errors: Record<keyof LoginValues, string> = {};

      // 用户名验证
      const usernameError = compose(
        ValidationRules.required('请输入用户名'),
        ValidationRules.minLength(3),
        ValidationRules.maxLength(20)
      )(values.username);
      if (usernameError) errors.username = usernameError;

      // 密码验证
      const passwordError = compose(
        ValidationRules.required('请输入密码'),
        ValidationRules.minLength(6)
      )(values.password);
      if (passwordError) errors.password = passwordError;

      return errors;
    },
    onSubmit: useFormikSubmit(async (values) => {
      // 模拟 API 调用
      await new Promise(resolve => setTimeout(resolve, 1500));
      console.log('登录成功:', values);
      alert(`欢迎, ${values.username}!`);
    }, 'login-submit')
  });

  // 获取字段属性
  const usernameProps = formik.getFieldProps('username');
  const passwordProps = formik.getFieldProps('password');

  return (
    <ScrollView style={styles.container} contentContainerStyle={styles.content}>
      <Text style={styles.title}>登录账户</Text>

      {/* 用户名 */}
      <View style={styles.fieldGroup}>
        <Text style={styles.label}>用户名</Text>
        <TextInput
          style={[
            styles.input,
            usernameProps.hasError && styles.inputError
          ]}
          value={usernameProps.value}
          onChangeText={usernameProps.onChange}
          onBlur={usernameProps.onBlur}
          placeholder="请输入用户名"
          placeholderTextColor="#999"
          autoCapitalize="none"
          autoCorrect={false}
        />
        {usernameProps.hasError && usernameProps.error && (
          <Text style={styles.error}>{usernameProps.error}</Text>
        )}
      </View>

      {/* 密码 */}
      <View style={styles.fieldGroup}>
        <Text style={styles.label}>密码</Text>
        <TextInput
          style={[
            styles.input,
            passwordProps.hasError && styles.inputError
          ]}
          value={passwordProps.value}
          onChangeText={passwordProps.onChange}
          onBlur={passwordProps.onBlur}
          placeholder="请输入密码"
          placeholderTextColor="#999"
          secureTextEntry
          autoCapitalize="none"
          autoCorrect={false}
        />
        {passwordProps.hasError && passwordProps.error && (
          <Text style={styles.error}>{passwordProps.error}</Text>
        )}
      </View>

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

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

      {/* 重置按钮 */}
      <TouchableOpacity
        style={styles.resetButton}
        onPress={formik.handleReset}
        disabled={formik.isSubmitting}
      >
        <Text style={styles.resetButtonText}>重置</Text>
      </TouchableOpacity>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5'
  },
  content: {
    padding: 20
  },
  title: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 32
  },
  fieldGroup: {
    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: '#32ADE6',
    borderColor: '#32ADE6'
  },
  checkboxLabel: {
    fontSize: 14,
    color: '#666'
  },
  submitButton: {
    backgroundColor: '#32ADE6',
    borderRadius: 12,
    paddingVertical: 16,
    alignItems: 'center',
    marginBottom: 12
  },
  submitButtonDisabled: {
    backgroundColor: '#ccc'
  },
  submitButtonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '700'
  },
  resetButton: {
    paddingVertical: 12,
    alignItems: 'center'
  },
  resetButtonText: {
    color: '#32ADE6',
    fontSize: 14
  }
});

9. 最佳实践总结

9.1 useFormik 与 useForm 对比

特性 useFormik useForm
API 风格 Formik 风格 简洁风格
状态管理 完整状态机 轻量状态
验证方式 函数式 规则式
学习曲线 陡峭 平缓
包体积 较大 较小

9.2 OpenHarmony 适配检查清单

项目 检查点 优先级
键盘管理 使用 Keyboard.dismiss()
后台任务 注册 TaskPool 任务
防抖验证 设置 500ms 防抖
无障碍 添加 accessibilityLabel
错误提示 自定义组件替代 Toast

9.3 性能优化建议

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                   useFormik 性能优化架构                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐     │
│  │  渲染优化     │    │  验证优化     │    │  数据流优化   │     │
│  │              │    │              │    │              │     │
│  │ • React.memo  │    │ • 防抖验证   │    │ • 批量更新   │     │
│  │ • 懒加载      │    │ • 错误缓存   │    │ • 状态压缩   │     │
│  │ • 虚拟列表    │    │ • TaskPool    │    │ • 数据压缩   │     │
│  └──────────────┘    └──────────────┘    └──────────────┘     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

参考资料


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

📕个人领域 :Linux/C++/java/AI

🚀 个人主页有点流鼻涕 · CSDN

💬 座右铭 : "向光而行,沐光而生。"

相关推荐
果粒蹬i10 小时前
【HarmonyOS】DAY7:鸿蒙跨平台 Tab 开发问题与列表操作难点深度复盘
华为·harmonyos
王码码203510 小时前
Flutter for OpenHarmony 实战之基础组件:第二十七篇 BottomSheet — 动态底部弹窗与底部栏菜单
android·flutter·harmonyos
ITUnicorn11 小时前
【HarmonyOS6】ArkTS 自定义组件封装实战:动画水杯组件
华为·harmonyos·arkts·鸿蒙·harmonyos6
全栈探索者11 小时前
@Component + struct = 你的新函数组件——React 开发者的鸿蒙入门指南(第 2 期)
react·harmonyos·arkts·前端开发·deveco studio·鸿蒙next·函数组件
廖松洋(Alina)11 小时前
【收尾以及复盘】flutter开发鸿蒙APP之成就徽章页面
flutter·华为·开源·harmonyos·鸿蒙
廖松洋(Alina)12 小时前
【收尾以及复盘】flutter开发鸿蒙APP之打卡日历页面
flutter·华为·开源·harmonyos·鸿蒙
廖松洋(Alina)12 小时前
【收尾以及复盘】flutter开发鸿蒙APP之本月数据统计页面
flutter·华为·开源·harmonyos·鸿蒙
果粒蹬i12 小时前
【HarmonyOS】DAY8:React Native for OpenHarmony 实战:多端响应式布局与高可用交互设计
react native·交互·harmonyos
讯方洋哥21 小时前
HarmonyOS App开发——职前通应用App开发(下)
华为·harmonyos