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 实现跨平台表单管理。
目录
- [Formik 表单框架介绍](#Formik 表单框架介绍)
- [TypeScript 类型系统设计](#TypeScript 类型系统设计)
- [核心 useFormik Hook 实现](#核心 useFormik Hook 实现)
- 表单验证架构
- [OpenHarmony 平台适配](#OpenHarmony 平台适配)
- 后台任务管理
- 无障碍支持
- 完整使用示例
- 最佳实践总结
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 │ │ • 数据压缩 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
参考资料
- 完整项目 Demo:https://atomgit.com/lbbxmx111/AtomGitNewsDemo
- OpenHarmony 跨平台社区:https://openharmonycrossplatform.csdn.net
- Formik 官方文档:https://formik.org
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
📕个人领域 :Linux/C++/java/AI
🚀 个人主页 :有点流鼻涕 · CSDN
💬 座右铭 : "向光而行,沐光而生。"
