OpenHarmony + RN:自定义 useForm 表单管理(改写版)

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

- [OpenHarmony + RN:自定义 useForm 表单管理(改写版)](#OpenHarmony + RN:自定义 useForm 表单管理(改写版))
-
- 摘要
- 目录
- [1. 表单管理需求分析](#1. 表单管理需求分析)
-
- [1.1 传统表单管理痛点](#1.1 传统表单管理痛点)
- [1.2 OpenHarmony 平台特殊因素](#1.2 OpenHarmony 平台特殊因素)
- [2. TypeScript 类型系统设计](#2. TypeScript 类型系统设计)
- [3. 核心 useForm Hook 实现](#3. 核心 useForm Hook 实现)
- [4. 表单验证器实现](#4. 表单验证器实现)
- [5. OpenHarmony 平台适配](#5. OpenHarmony 平台适配)
- [6. 键盘管理工具](#6. 键盘管理工具)
- [7. 完整使用示例](#7. 完整使用示例)
-
- [7.1 登录表单组件](#7.1 登录表单组件)
- [7.2 注册表单组件(带密码确认)](#7.2 注册表单组件(带密码确认))
- [8. 最佳实践总结](#8. 最佳实践总结)
-
- [8.1 表单管理最佳实践](#8.1 表单管理最佳实践)
- [8.2 OpenHarmony 适配检查清单](#8.2 OpenHarmony 适配检查清单)
- [8.3 性能优化建议](#8.3 性能优化建议)
- 参考资料
摘要
本文深入探讨在 OpenHarmony 6.0.0 平台上使用 React Native 实现自定义表单管理 Hook (useForm) 的完整方案。通过 useForm 的设计,我们解决了表单状态管理、验证规则和提交处理三大核心问题,并针对 OpenHarmony 6.0.0 平台的特殊需求进行了优化适配。
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
目录
- 表单管理需求分析
- [TypeScript 类型系统设计](#TypeScript 类型系统设计)
- [核心 useForm Hook 实现](#核心 useForm Hook 实现)
- 表单验证器实现
- [OpenHarmony 平台适配](#OpenHarmony 平台适配)
- 键盘管理工具
- 完整使用示例
- 最佳实践总结
1. 表单管理需求分析
1.1 传统表单管理痛点
| 痛点 | 描述 | 影响 |
|---|---|---|
| 状态分散 | 表单字段状态分散在组件各处 | 代码维护困难 |
| 验证冗余 | 需要为每个字段单独编写验证逻辑 | 代码重复率高 |
| 提交耦合 | 提交逻辑与 UI 组件紧密耦合 | 难以复用 |
| 跨平台差异 | 不同平台输入行为不一致 | 兼容性问题 |
1.2 OpenHarmony 平台特殊因素
┌─────────────────────────────────────────────────────────────────┐
│ OpenHarmony 表单特殊考量 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │
│ │ 输入组件行为 │ │ 性能优化需求 │ │ 异步提交处理 │ │
│ │ │ │ │ │ │ │
│ │ • 焦点管理差异 │ │ • 频繁状态更新 │ │ • 网络模型 │ │
│ │ • 键盘行为不同 │ │ • 渲染性能敏感 │ │ • 超时处理 │ │
│ │ • 输入法处理 │ │ • 批量更新需求 │ │ • 重试机制 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2. TypeScript 类型系统设计
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>;
3. 核心 useForm Hook 实现
typescript
// ============================================
// 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;
4. 表单验证器实现
typescript
// ============================================
// 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 || '此字段为必填项';
}
};
}
}
5. OpenHarmony 平台适配
typescript
// ============================================
// 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>
);
}
6. 键盘管理工具
typescript
// ============================================
// 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. 完整使用示例
7.1 登录表单组件
typescript
// ============================================
// 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 注册表单组件(带密码确认)
typescript
// ============================================
// 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. 最佳实践总结
8.1 表单管理最佳实践
┌─────────────────────────────────────────────────────────────────┐
│ useForm 最佳实践架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ 数据层 │ │ 验证层 │ │
│ │ │ │ │ │
│ │ • initialValues │ │ • validationRules │ │
│ │ • 状态管理 │ │ • 同步验证 │ │
│ │ • 脏数据检测 │ │ • 异步验证 │ │
│ └────────────────────┘ └────────────────────┘ │
│ │ │ │
│ └──────────┬───────────────┘ │
│ ▼ │
│ ┌────────────────────┐ │
│ │ useForm Hook │ │
│ │ │ │
│ │ • 状态追踪 │ │
│ │ • 防抖处理 │ │
│ │ • 提交管理 │ │
│ └────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ UI 组件层 │ │
│ │ │ │
│ │ • TextInput • CheckBox • Select • DatePicker │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
8.2 OpenHarmony 适配检查清单
| 项目 | 检查点 | 说明 |
|---|---|---|
| 焦点管理 | 外部点击失焦 | 使用 FocusManager 处理 |
| 键盘处理 | 键盘避让 | 使用 SmartKeyboardAvoidingView |
| 输入验证 | 防抖延迟 | 设置 500ms 防抖 |
| 滚动定位 | 输入框可见 | 使用 InputScrollManager |
| 提交优化 | 超时重试 | 实现指数退避机制 |
| 状态持久化 | 暂停保存 | 保存表单到持久化存储 |
8.3 性能优化建议
-
状态更新优化
- 使用
useCallback缓存回调函数 - 使用防抖减少频繁验证
- 批量更新多个字段值
- 使用
-
验证优化
- 按需验证(onBlur 或 onChange)
- 预编译正则表达式
- 异步验证使用 Web Worker
-
渲染优化
- 使用 React.memo 包装表单字段
- 避免在渲染中创建对象/函数
- 使用 useRef 存储非渲染数据
参考资料
- 完整项目 Demo:https://atomgit.com/lbbxmx111/AtomGitNewsDemo
- OpenHarmony 跨平台社区:https://openharmonycrossplatform.csdn.net
- React Native for OpenHarmony:https://github.com/react-native-oh-library
📕个人领域 :Linux/C++/java/AI
🚀 个人主页 :有点流鼻涕 · CSDN
💬 座右铭 : "向光而行,沐光而生。"
