【HarmonyOS实战】React Native Formik 表单开发实践 - OpenHarmony 平台兼容性解决方案
本文介绍了在OpenHarmony平台上使用Formik表单管理库的关键问题和优化方案。主要包含三个平台适配问题:输入法事件差异导致验证时机不准确、键盘管理机制造成布局遮挡、无障碍支持不足影响错误提示播报。文章提供了完整的TypeScript实现代码,包括表单验证规则、密码强度计算和状态管理Hook。特别推荐使用validateOnBlur配置,既能提升用户体验(减少输入干扰),又能优化性能(降低
·
前言
Formik 作为 React 生态中久经考验的表单管理库,能够大幅简化状态管理,减少约 60% 的样板代码。但在 OpenHarmony 平台使用时,需特别注意以下关键问题:
以下是整理后的表格形式:
| 问题 | 影响 |
|---|---|
| 输入法事件差异 | 验证时机不准确 |
| 键盘管理机制 | 布局遮挡输入框 |
| 无障碍支持 | 错误提示无法播报 |
快速开始
关键配置
import { Formik } from 'formik';
import * as yup from 'yup';
// Yup 验证模式
const schema = yup.object().shape({
email: yup.string().email('请输入有效的邮箱').required('邮箱不能为空'),
password: yup.string()
.min(8, '密码至少8个字符')
.matches(/[a-z]/, '必须包含小写字母')
.matches(/[A-Z]/, '必须包含大写字母')
.matches(/[0-9]/, '必须包含数字')
.required('密码不能为空'),
confirmPassword: yup.string()
.oneOf([yup.ref('password')], '两次密码不一致')
.required('请确认密码'),
});
// Formik 组件使用
<Formik
initialValues={{ email: '', password: '', confirmPassword: '' }}
validationSchema={schema}
validateOnBlur={true} // OpenHarmony 推荐:失焦验证
validateOnChange={false} // 减少频繁验证
onSubmit={handleSubmit}
>
{({ handleChange, handleBlur, values, errors, touched }) => (
// 表单内容
)}
</Formik>
为什么使用 validateOnBlur?
- 提升用户体验
- 避免用户在输入过程中频繁看到错误提示
- 减少界面干扰,让用户专注于当前输入
- 性能优化
- 减少实时校验带来的计算开销
- 只在必要时触发校验逻辑
- 逻辑合理性
- 用户完成输入后才进行校验更符合实际场景
- 避免未完成输入就被判定为错误的情况
- 移动端适配
- 在移动设备上减少虚拟键盘与校验提示的冲突
- 避免因实时校验导致的输入卡顿
- 错误处理
- 集中处理错误信息,便于用户一次性修正
- 减少重复错误提示带来的困扰
完整实现
表单组件代码
/**
* Formik 表单验证 - OpenHarmony 适配版
*
* @platform OpenHarmony 6.0.0 (API 20)
* @react-native 0.72.5
* @typescript 5.0+
*/
import React, { useCallback, useMemo } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ScrollView,
TextInput,
KeyboardAvoidingView,
Platform,
ActivityIndicator,
} from 'react-native';
// ==================== 类型定义 ====================
interface FormValues {
email: string;
password: string;
confirmPassword: string;
agreeTerms: boolean;
}
interface FormErrors {
email?: string;
password?: string;
confirmPassword?: string;
agreeTerms?: string;
}
interface FormTouched {
email?: boolean;
password?: boolean;
confirmPassword?: boolean;
agreeTerms?: boolean;
}
interface Props {
onSubmit?: (values: FormValues) => Promise<void>;
onBack?: () => void;
}
// ==================== 验证规则 ====================
/**
* 邮箱验证
*/
const validateEmail = (email: string): string | undefined => {
if (!email) return '邮箱不能为空';
const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
if (!emailRegex.test(email)) return '请输入有效的邮箱地址';
};
/**
* 密码强度等级
*/
enum PasswordStrength {
None = 0,
Weak = 1,
Fair = 2,
Good = 3,
Strong = 4,
}
/**
* 计算密码强度
*/
const getPasswordStrength = (password: string): PasswordStrength => {
let score = 0;
if (password.length >= 8) score++;
if (password.length >= 12) score++;
if (/[a-z]/.test(password) && /[A-Z]/.test(password)) score++;
if (/[0-9]/.test(password)) score++;
if (/[^a-zA-Z0-9]/.test(password)) score++;
return Math.min(score, 4) as PasswordStrength;
};
/**
* 密码强度配置
*/
const STRENGTH_CONFIG = {
[PasswordStrength.None]: { label: '无', color: '#d1d5db', percent: 0 },
[PasswordStrength.Weak]: { label: '弱', color: '#ef4444', percent: 25 },
[PasswordStrength.Fair]: { label: '一般', color: '#f59e0b', percent: 50 },
[PasswordStrength.Good]: { label: '良好', color: '#10b981', percent: 75 },
[PasswordStrength.Strong]: { label: '强', color: '#3b82f6', percent: 100 },
};
/**
* 密码验证
*/
const validatePassword = (password: string): string | undefined => {
if (!password) return '密码不能为空';
if (password.length < 8) return '密码至少8个字符';
if (!/[a-z]/.test(password)) return '必须包含小写字母';
if (!/[A-Z]/.test(password)) return '必须包含大写字母';
if (!/[0-9]/.test(password)) return '必须包含数字';
};
/**
* 确认密码验证
*/
const validateConfirmPassword = (
confirmPassword: string,
password: string
): string | undefined => {
if (!confirmPassword) return '请确认密码';
if (confirmPassword !== password) return '两次输入的密码不一致';
};
// ==================== 自定义 Hooks ====================
/**
* 表单状态管理 Hook
* 封装 Formik 核心逻辑
*/
const useFormikState = (initialValues: FormValues) => {
const [values, setValues] = React.useState<FormValues>(initialValues);
const [errors, setErrors] = React.useState<FormErrors>({});
const [touched, setTouched] = React.useState<FormTouched>({});
const [isSubmitting, setIsSubmitting] = React.useState(false);
const [submitCount, setSubmitCount] = React.useState(0);
/**
* 计算表单是否有效
*/
const isValid = useMemo(() => {
return !!(
values.email &&
!validateEmail(values.email) &&
values.password &&
!validatePassword(values.password) &&
values.confirmPassword === values.password &&
values.agreeTerms
);
}, [values]);
/**
* 获取密码强度
*/
const passwordStrength = useMemo(() => {
return STRENGTH_CONFIG[getPasswordStrength(values.password)];
}, [values.password]);
/**
* 处理值变更
*/
const handleChange = useCallback(
(field: keyof FormValues) => (value: string | boolean) => {
setValues(prev => ({ ...prev, [field]: value }));
// 仅在字段已被触摸时验证
if (touched[field]) {
let error: string | undefined;
if (field === 'email') error = validateEmail(value as string);
else if (field === 'password') error = validatePassword(value as string);
else if (field === 'confirmPassword')
error = validateConfirmPassword(value as string, values.password);
else if (field === 'agreeTerms' && !value)
error = '请同意服务条款';
setErrors(prev => ({ ...prev, [field]: error }));
}
},
[touched, values.password]
);
/**
* 处理失焦
*/
const handleBlur = useCallback(
(field: keyof FormValues) => () => {
setTouched(prev => ({ ...prev, [field]: true }));
let error: string | undefined;
if (field === 'email') error = validateEmail(values.email);
else if (field === 'password') error = validatePassword(values.password);
else if (field === 'confirmPassword')
error = validateConfirmPassword(values.confirmPassword, values.password);
setErrors(prev => ({ ...prev, [field]: error }));
},
[values]
);
/**
* 处理条款切换
*/
const handleToggleTerms = useCallback(() => {
const newValue = !values.agreeTerms;
handleChange('agreeTerms')(newValue);
setTouched(prev => ({ ...prev, agreeTerms: true }));
}, [values.agreeTerms, handleChange]);
/**
* 提交表单
*/
const handleSubmit = useCallback(
async (onSubmit?: (values: FormValues) => Promise<void>) => {
setIsSubmitting(true);
setSubmitCount(prev => prev + 1);
// 标记所有字段为已触摸
setTouched({
email: true,
password: true,
confirmPassword: true,
agreeTerms: true,
});
// 执行所有验证
const newErrors: FormErrors = {
email: validateEmail(values.email),
password: validatePassword(values.password),
confirmPassword: validateConfirmPassword(values.confirmPassword, values.password),
agreeTerms: values.agreeTerms ? undefined : '请同意服务条款',
};
setErrors(newErrors);
const hasErrors = Object.values(newErrors).some(e => e !== undefined);
if (hasErrors) {
setIsSubmitting(false);
return false;
}
try {
if (onSubmit) {
await onSubmit(values);
}
return true;
} finally {
setIsSubmitting(false);
}
},
[values]
);
/**
* 重置表单
*/
const reset = useCallback(() => {
setValues(initialValues);
setErrors({});
setTouched({});
setSubmitCount(0);
}, [initialValues]);
return {
// 状态
values,
errors,
touched,
isSubmitting,
submitCount,
isValid,
passwordStrength,
// 操作
handleChange,
handleBlur,
handleToggleTerms,
handleSubmit,
reset,
};
};
// ==================== 表单组件 ====================
const FormikFormScreen: React.FC<Props> = ({ onSubmit, onBack }) => {
const initialValues: FormValues = {
email: '',
password: '',
confirmPassword: '',
agreeTerms: false,
};
const form = useFormikState(initialValues);
/**
* 处理提交
*/
const handleSubmit = useCallback(async () => {
const success = await form.handleSubmit(onSubmit);
if (success) {
alert('注册成功!');
form.reset();
}
}, [form, onSubmit]);
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
style={styles.container}
>
<ScrollView
style={styles.scrollView}
keyboardShouldPersistTaps="handled"
contentContainerStyle={styles.scrollContent}
>
{/* 头部 */}
<View style={styles.header}>
{onBack && (
<TouchableOpacity onPress={onBack} style={styles.backButton}>
<Text style={styles.backButtonText}>← 返回</Text>
</TouchableOpacity>
)}
<View style={styles.headerContent}>
<Text style={styles.headerTitle}>用户注册</Text>
<Text style={styles.headerSubtitle}>Formik + Yup 表单验证</Text>
</View>
</View>
{/* 平台信息 */}
<View style={styles.platformBar}>
<Text style={styles.platformText}>
{Platform.OS.toUpperCase()} • OpenHarmony 6.0.0
</Text>
</View>
{/* 表单卡片 */}
<View style={styles.formCard}>
<Text style={styles.formTitle}>创建账号</Text>
{/* 邮箱 */}
<FormField
label="邮箱地址"
placeholder="请输入邮箱地址"
value={form.values.email}
error={form.errors.email}
touched={form.touched.email}
onChangeText={form.handleChange('email')}
onBlur={form.handleBlur('email')}
keyboardType="email-address"
autoCapitalize="none"
disabled={form.isSubmitting}
icon="📧"
/>
{/* 密码 */}
<PasswordField
label="密码"
placeholder="请输入密码"
value={form.values.password}
error={form.errors.password}
touched={form.touched.password}
onChangeText={form.handleChange('password')}
onBlur={form.handleBlur('password')}
disabled={form.isSubmitting}
strength={form.passwordStrength}
/>
{/* 确认密码 */}
<FormField
label="确认密码"
placeholder="请再次输入密码"
value={form.values.confirmPassword}
error={form.errors.confirmPassword}
touched={form.touched.confirmPassword}
onChangeText={form.handleChange('confirmPassword')}
onBlur={form.handleBlur('confirmPassword')}
secureTextEntry
disabled={form.isSubmitting}
icon="🔒"
/>
{/* 服务条款 */}
<View style={styles.termsContainer}>
<TouchableOpacity
style={styles.checkbox}
onPress={form.handleToggleTerms}
disabled={form.isSubmitting}
activeOpacity={0.7}
>
<View style={[styles.checkboxBox, form.values.agreeTerms && styles.checkboxChecked]}>
{form.values.agreeTerms && <Text style={styles.checkmark}>✓</Text>}
</View>
<Text style={styles.termsText}>
我已阅读并同意
<Text style={styles.termsLink}>《服务条款》</Text>
和
<Text style={styles.termsLink}>《隐私政策》</Text>
</Text>
</TouchableOpacity>
{form.errors.agreeTerms && form.touched.agreeTerms && (
<Text style={styles.errorText}>{form.errors.agreeTerms}</Text>
)}
</View>
{/* 提交按钮 */}
<TouchableOpacity
style={[styles.submitButton, !form.isValid && styles.submitButtonDisabled]}
onPress={handleSubmit}
disabled={!form.isValid || form.isSubmitting}
activeOpacity={0.8}
>
{form.isSubmitting ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.submitButtonText}>立即注册</Text>
)}
</TouchableOpacity>
{/* 表单状态 */}
<View style={styles.formStatus}>
<StatusBadge label="提交次数" value={form.submitCount} />
<StatusBadge
label="表单状态"
value={form.isValid ? '有效' : '无效'}
status={form.isValid ? 'success' : 'error'}
/>
</View>
</View>
{/* 验证规则说明 */}
<View style={styles.rulesCard}>
<Text style={styles.rulesTitle}>密码要求</Text>
<RuleItem icon="📏" text="至少 8 个字符" />
<RuleItem icon="🔡" text="包含小写字母" />
<RuleItem icon="🔠" text="包含大写字母" />
<RuleItem icon="🔢" text="包含数字" />
</View>
{/* OpenHarmony 适配提示 */}
<View style={styles.tipsCard}>
<Text style={styles.tipsTitle}>OpenHarmony 适配要点</Text>
<TipItem
icon="validateOnBlur"
title="失焦验证模式"
desc="使用 validateOnBlur 避免输入法频繁触发验证"
/>
<TipItem
icon="KeyboardAvoidingView"
title="键盘避让处理"
desc="OpenHarmony 设备需调整 KeyboardAvoidingView 的 behavior"
/>
<TipItem
icon="accessibilityLiveRegion"
title="无障碍支持"
desc="错误信息需添加 accessibilityLiveRegion 属性"
/>
</View>
</ScrollView>
</KeyboardAvoidingView>
);
};
// ==================== 子组件 ====================
interface FormFieldProps {
label: string;
placeholder: string;
value: string;
error?: string;
touched?: boolean;
onChangeText: (text: string) => void;
onBlur: () => void;
keyboardType?: 'email-address' | 'default';
autoCapitalize?: 'none' | 'sentences';
secureTextEntry?: boolean;
disabled?: boolean;
icon?: string;
}
const FormField: React.FC<FormFieldProps> = React.memo(({
label,
placeholder,
value,
error,
touched,
onChangeText,
onBlur,
keyboardType = 'default',
autoCapitalize = 'none',
secureTextEntry = false,
disabled = false,
icon,
}) => {
const showError = touched && error;
return (
<View style={styles.fieldContainer}>
<Text style={styles.fieldLabel}>
{icon && <Text style={styles.fieldIcon}>{icon}</Text>}
{label}
</Text>
<TextInput
style={[styles.fieldInput, showError && styles.fieldInputError]}
placeholder={placeholder}
value={value}
onChangeText={onChangeText}
onBlur={onBlur}
keyboardType={keyboardType}
autoCapitalize={autoCapitalize}
secureTextEntry={secureTextEntry}
editable={!disabled}
placeholderTextColor="#9ca3af"
accessibilityLabel={label}
accessibilityLiveRegion={showError ? 'polite' : 'none'}
accessibilityHint={showError ? error : undefined}
/>
{showError && (
<Text
style={styles.errorText}
accessibilityLiveRegion="polite"
accessibilityLabel={`错误: ${error}`}
>
{error}
</Text>
)}
</View>
);
});
interface PasswordFieldProps extends Omit<FormFieldProps, 'icon'> {
strength: { label: string; color: string; percent: number };
}
const PasswordField: React.FC<PasswordFieldProps> = React.memo((props) => {
const { strength, ...fieldProps } = props;
const showError = props.touched && props.error;
return (
<View style={styles.fieldContainer}>
<Text style={styles.fieldLabel}>
<Text style={styles.fieldIcon}>🔐</Text>
{props.label}
</Text>
<TextInput
style={[styles.fieldInput, showError && styles.fieldInputError]}
placeholder={props.placeholder}
value={props.value}
onChangeText={props.onChangeText}
onBlur={props.onBlur}
secureTextEntry
editable={!props.disabled}
placeholderTextColor="#9ca3af"
/>
{showError && (
<Text style={styles.errorText} accessibilityLiveRegion="polite">
{props.error}
</Text>
)}
{/* 密码强度指示器 */}
{props.value.length > 0 && !showError && (
<View style={styles.strengthContainer}>
<View style={styles.strengthBar}>
<View
style={[
styles.strengthFill,
{ backgroundColor: strength.color, width: `${strength.percent}%` },
]}
/>
</View>
<Text style={[styles.strengthText, { color: strength.color }]}>
强度: {strength.label}
</Text>
</View>
)}
</View>
);
});
interface StatusBadgeProps {
label: string;
value: string | number;
status?: 'default' | 'success' | 'error';
}
const StatusBadge: React.FC<StatusBadgeProps> = ({ label, value, status = 'default' }) => {
const statusColors = {
default: '#6b7280',
success: '#10b981',
error: '#ef4444',
};
return (
<View style={styles.statusBadge}>
<Text style={styles.statusLabel}>{label}</Text>
<Text style={[styles.statusValue, { color: statusColors[status] }]}>
{value}
</Text>
</View>
);
};
interface RuleItemProps {
icon: string;
text: string;
}
const RuleItem: React.FC<RuleItemProps> = ({ icon, text }) => (
<View style={styles.ruleItem}>
<Text style={styles.ruleIcon}>{icon}</Text>
<Text style={styles.ruleText}>{text}</Text>
</View>
);
interface TipItemProps {
icon: string;
title: string;
desc: string;
}
const TipItem: React.FC<TipItemProps> = ({ icon, title, desc }) => (
<View style={styles.tipItem}>
<View style={styles.tipIconBox}>
<Text style={styles.tipIcon}>{icon}</Text>
</View>
<View style={styles.tipContent}>
<Text style={styles.tipTitle}>{title}</Text>
<Text style={styles.tipDesc}>{desc}</Text>
</View>
</View>
);
// ==================== 样式定义 ====================
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f9fafb',
},
scrollView: {
flex: 1,
},
scrollContent: {
paddingBottom: 24,
},
// 头部
header: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: '#7c3aed',
},
backButton: {
padding: 8,
marginRight: 8,
},
backButtonText: {
color: '#fff',
fontSize: 15,
},
headerContent: {
flex: 1,
},
headerTitle: {
fontSize: 18,
fontWeight: '700',
color: '#fff',
},
headerSubtitle: {
fontSize: 12,
color: 'rgba(255,255,255,0.8)',
marginTop: 2,
},
// 平台信息
platformBar: {
paddingVertical: 10,
paddingHorizontal: 16,
backgroundColor: '#ede9fe',
alignItems: 'center',
},
platformText: {
fontSize: 12,
color: '#6d28d9',
fontWeight: '500',
},
// 表单卡片
formCard: {
margin: 16,
backgroundColor: '#fff',
borderRadius: 16,
padding: 20,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08,
shadowRadius: 8,
elevation: 3,
},
formTitle: {
fontSize: 20,
fontWeight: '700',
color: '#111827',
marginBottom: 20,
},
// 表单字段
fieldContainer: {
marginBottom: 16,
},
fieldLabel: {
fontSize: 14,
fontWeight: '600',
color: '#374151',
marginBottom: 8,
},
fieldIcon: {
marginRight: 6,
},
fieldInput: {
borderWidth: 1.5,
borderColor: '#e5e7eb',
borderRadius: 12,
padding: 14,
fontSize: 15,
backgroundColor: '#f9fafb',
color: '#111827',
},
fieldInputError: {
borderColor: '#ef4444',
backgroundColor: '#fef2f2',
},
errorText: {
color: '#ef4444',
fontSize: 12,
marginTop: 6,
marginLeft: 4,
},
// 密码强度
strengthContainer: {
marginTop: 8,
},
strengthBar: {
height: 4,
backgroundColor: '#e5e7eb',
borderRadius: 2,
overflow: 'hidden',
},
strengthFill: {
height: '100%',
borderRadius: 2,
},
strengthText: {
fontSize: 11,
marginTop: 4,
fontWeight: '500',
},
// 服务条款
termsContainer: {
marginBottom: 16,
},
checkbox: {
flexDirection: 'row',
alignItems: 'flex-start',
},
checkboxBox: {
width: 22,
height: 22,
borderRadius: 6,
borderWidth: 2,
borderColor: '#d1d5db',
alignItems: 'center',
justifyContent: 'center',
marginRight: 10,
marginTop: 1,
},
checkboxChecked: {
backgroundColor: '#7c3aed',
borderColor: '#7c3aed',
},
checkmark: {
color: '#fff',
fontSize: 12,
fontWeight: '700',
},
termsText: {
flex: 1,
fontSize: 13,
color: '#4b5563',
lineHeight: 20,
},
termsLink: {
color: '#7c3aed',
fontWeight: '500',
},
// 提交按钮
submitButton: {
backgroundColor: '#7c3aed',
borderRadius: 12,
padding: 16,
alignItems: 'center',
marginTop: 8,
minHeight: 52,
justifyContent: 'center',
},
submitButtonDisabled: {
backgroundColor: '#d1d5db',
},
submitButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
// 表单状态
formStatus: {
flexDirection: 'row',
gap: 10,
marginTop: 16,
paddingTop: 16,
borderTopWidth: 1,
borderTopColor: '#f3f4f6',
},
statusBadge: {
flex: 1,
backgroundColor: '#f3f4f6',
borderRadius: 8,
padding: 10,
alignItems: 'center',
},
statusLabel: {
fontSize: 11,
color: '#6b7280',
marginBottom: 2,
},
statusValue: {
fontSize: 14,
fontWeight: '600',
},
// 规则卡片
rulesCard: {
margin: 16,
marginTop: 0,
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
},
rulesTitle: {
fontSize: 14,
fontWeight: '600',
color: '#111827',
marginBottom: 12,
},
ruleItem: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
},
ruleIcon: {
fontSize: 16,
marginRight: 10,
},
ruleText: {
fontSize: 13,
color: '#4b5563',
},
// 提示卡片
tipsCard: {
margin: 16,
marginTop: 0,
backgroundColor: '#fef3c7',
borderRadius: 12,
padding: 16,
borderLeftWidth: 4,
borderLeftColor: '#f59e0b',
},
tipsTitle: {
fontSize: 14,
fontWeight: '600',
color: '#92400e',
marginBottom: 12,
},
tipItem: {
flexDirection: 'row',
marginBottom: 12,
},
tipItem: {
flexDirection: 'row',
marginBottom: 12,
},
tipIconBox: {
width: 32,
height: 32,
borderRadius: 8,
backgroundColor: '#fde68a',
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
tipIcon: {
fontSize: 12,
fontWeight: '600',
color: '#92400e',
},
tipContent: {
flex: 1,
},
tipTitle: {
fontSize: 13,
fontWeight: '600',
color: '#78350f',
marginBottom: 2,
},
tipDesc: {
fontSize: 12,
color: '#92400e',
lineHeight: 18,
},
});
export default FormikFormScreen;
代码优化说明
以下是改进后的表格形式:
| 改进点 | 原文描述 | 改进方案 |
|---|---|---|
| 类型定义 | 分散在组件内 | 统一提取,使用 TypeScript 5.0+ |
| 验证逻辑 | 硬编码在函数中 | 独立验证函数,便于单元测试 |
| 状态管理 | 多个 useState | 封装为 useFormikState Hook |
| 组件复用 | 内联渲染 | 拆分为 FormField、PasswordField 等组件 |
| 无障碍 | 无 | 添加 accessibilityLiveRegion 支持 |
| 密码强度 | 简单判断 | 枚举 + 配置化,易于扩展 |
OpenHarmony 系统适配指南
- 输入事件处理
// 推荐:失焦验证
validateOnBlur={true}
validateOnChange={false}
// 避免:即时验证(OpenHarmony 输入法会频繁触发)
validateOnChange={true}
- 键盘避让
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
// OpenHarmony 不需要 behavior,使用默认值即可
>
- 无障碍支持
<Text
accessibilityLiveRegion="polite"
accessibilityLabel={`错误: ${error}`}
accessibilityHint={showError ? error : undefined}
>
{error}
</Text>
- 权限配置
// harmony/entry/src/main/module.json5
{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
]
}
}
验证规则速查指南
| Yup 方法 | 用途 | 示例 |
|---|---|---|
| required() | 必填验证 | yup.string().required(‘不能为空’) |
| email() | 邮箱格式验证 | yup.string().email(‘邮箱格式错误’) |
| min() | 最小长度限制 | yup.string().min(8, ‘至少8个字符’) |
| matches() | 正则表达式验证 | yup.string().matches(/[a-z]/, ‘需包含小写字母’) |
| oneOf() | 值匹配验证 | yup.string().oneOf([yup.ref(‘password’)], ‘密码不一致’)Yup 方法 用途 示例 |
总结
优化后的版本:
验证策略:采用 validateOnBlur 机制,有效减少输入法触发的验证频率
状态管理:通过自定义 Hook 封装,实现逻辑的高效复用
无障碍支持:集成 accessibilityLiveRegion 属性,完善屏幕阅读器兼容性
组件设计:将 FormField 和 PasswordField 等拆分为独立组件,提升可维护性
类型系统:全面支持 TypeScript 类型检查,确保代码安全性
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)