Ignite表单验证:输入验证与错误提示机制
在现代移动应用开发中,表单验证是确保数据质量和用户体验的关键环节。Ignite作为业界领先的React Native样板工程,提供了一套完整且优雅的表单验证解决方案。本文将深入探讨Ignite中的表单验证机制,涵盖基础验证、错误处理、自定义验证规则以及最佳实践。
表单验证的核心组件
TextField组件:验证的基础载体
Ignite的TextField
组件是表单验证的核心,它扩展了React Native的原生TextInput
,提供了丰富的验证状态管理功能:
import { TextField } from "@/components/TextField"
const [email, setEmail] = useState("")
const [error, setError] = useState("")
<TextField
value={email}
onChangeText={setEmail}
status={error ? "error" : undefined}
helper={error}
label="邮箱地址"
placeholder="请输入邮箱"
autoCapitalize="none"
keyboardType="email-address"
/>
验证状态管理
Ignite通过status
属性管理表单验证状态,支持三种状态:
状态值 | 描述 | 视觉表现 |
---|---|---|
undefined | 正常状态 | 默认边框颜色 |
"error" | 错误状态 | 红色边框和错误提示 |
"disabled" | 禁用状态 | 灰色外观,不可编辑 |
验证实现模式
1. 基础验证模式
最简单的验证模式是在组件层面直接处理:
const validateEmail = (email: string) => {
if (!email) return "邮箱不能为空"
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return "邮箱格式不正确"
return ""
}
const LoginForm = () => {
const [email, setEmail] = useState("")
const [error, setError] = useState("")
const handleEmailChange = (text: string) => {
setEmail(text)
setError(validateEmail(text))
}
return (
<TextField
value={email}
onChangeText={handleEmailChange}
status={error ? "error" : undefined}
helper={error}
label="邮箱"
/>
)
}
2. Context-based验证模式
Ignite推荐使用React Context进行状态管理,如AuthContext中的实现:
// 在Context中定义验证逻辑
const useValidation = () => {
const [email, setEmail] = useState("")
const validationError = useMemo(() => {
if (!email) return "邮箱不能为空"
if (email.length < 6) return "邮箱至少6个字符"
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return "邮箱格式不正确"
return ""
}, [email])
return { email, setEmail, validationError }
}
3. 实时验证与提交验证结合
最佳实践是结合实时验证和提交时验证:
const RegistrationForm = () => {
const [formData, setFormData] = useState({
email: "",
password: "",
confirmPassword: ""
})
const [errors, setErrors] = useState({})
const [isSubmitted, setIsSubmitted] = useState(false)
// 实时验证
const validateField = (name: string, value: string) => {
const newErrors = { ...errors }
switch (name) {
case "email":
if (!value) newErrors.email = "邮箱不能为空"
else if (!/\S+@\S+\.\S+/.test(value)) newErrors.email = "邮箱格式不正确"
else delete newErrors.email
break
case "password":
if (value.length < 8) newErrors.password = "密码至少8位"
else delete newErrors.password
break
case "confirmPassword":
if (value !== formData.password) newErrors.confirmPassword = "密码不一致"
else delete newErrors.confirmPassword
break
}
setErrors(newErrors)
}
// 提交时完整验证
const validateForm = () => {
const newErrors = {}
if (!formData.email) newErrors.email = "邮箱不能为空"
else if (!/\S+@\S+\.\S+/.test(formData.email)) newErrors.email = "邮箱格式不正确"
if (formData.password.length < 8) newErrors.password = "密码至少8位"
if (formData.confirmPassword !== formData.password) {
newErrors.confirmPassword = "密码不一致"
}
setErrors(newErrors)
setIsSubmitted(true)
return Object.keys(newErrors).length === 0
}
const handleSubmit = () => {
if (validateForm()) {
// 提交逻辑
}
}
return (
<>
<TextField
value={formData.email}
onChangeText={(text) => {
setFormData({ ...formData, email: text })
validateField("email", text)
}}
status={errors.email ? "error" : undefined}
helper={errors.email}
label="邮箱"
/>
<TextField
value={formData.password}
onChangeText={(text) => {
setFormData({ ...formData, password: text })
validateField("password", text)
}}
secureTextEntry
status={errors.password ? "error" : undefined}
helper={errors.password}
label="密码"
/>
<TextField
value={formData.confirmPassword}
onChangeText={(text) => {
setFormData({ ...formData, confirmPassword: text })
validateField("confirmPassword", text)
}}
secureTextEntry
status={errors.confirmPassword ? "error" : undefined}
helper={errors.confirmPassword}
label="确认密码"
/>
<Button onPress={handleSubmit} title="注册" />
</>
)
}
高级验证技巧
自定义验证规则
创建可复用的验证器函数:
// validators.ts
export const validators = {
required: (value: string, message = "该字段不能为空") =>
!value ? message : "",
minLength: (value: string, length: number, message = `至少${length}个字符`) =>
value.length < length ? message : "",
maxLength: (value: string, length: number, message = `最多${length}个字符`) =>
value.length > length ? message : "",
email: (value: string, message = "邮箱格式不正确") =>
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? message : "",
pattern: (value: string, regex: RegExp, message = "格式不正确") =>
!regex.test(value) ? message : "",
match: (value: string, target: string, message = "不匹配") =>
value !== target ? message : ""
}
// 使用示例
const validatePassword = (password: string) => {
return (
validators.required(password, "密码不能为空") ||
validators.minLength(password, 8, "密码至少8位") ||
validators.pattern(password, /[A-Z]/, "必须包含大写字母") ||
validators.pattern(password, /[0-9]/, "必须包含数字") ||
""
)
}
异步验证支持
处理需要服务器验证的场景:
const useAsyncValidation = () => {
const [isChecking, setIsChecking] = useState(false)
const [asyncErrors, setAsyncErrors] = useState({})
const checkEmailAvailability = async (email: string) => {
if (!email) return
setIsChecking(true)
try {
const response = await api.checkEmail({ email })
if (!response.available) {
setAsyncErrors(prev => ({ ...prev, email: "邮箱已被注册" }))
} else {
setAsyncErrors(prev => ({ ...prev, email: undefined }))
}
} catch (error) {
setAsyncErrors(prev => ({ ...prev, email: "验证失败,请重试" }))
} finally {
setIsChecking(false)
}
}
return { isChecking, asyncErrors, checkEmailAvailability }
}
表单验证架构
对于复杂表单,建议使用分层架构:
国际化与可访问性
多语言错误消息
利用Ignite的i18n系统实现多语言错误提示:
import { translate } from "@/i18n/translate"
const ErrorMessages = {
required: (field: string) =>
translate("validation:required", { field }),
email: () => translate("validation:email"),
minLength: (length: number) =>
translate("validation:minLength", { length })
}
// 在语言文件中
// en.ts
export default {
validation: {
required: "{{field}} is required",
email: "Please enter a valid email address",
minLength: "Must be at least {{length}} characters"
}
}
// zh.ts
export default {
validation: {
required: "{{field}}不能为空",
email: "请输入有效的邮箱地址",
minLength: "至少需要{{length}}个字符"
}
}
可访问性支持
确保验证错误对屏幕阅读器友好:
<TextField
value={email}
onChangeText={setEmail}
status={error ? "error" : undefined}
helper={error}
label="邮箱"
accessibilityLabel="邮箱输入框"
accessibilityHint={error ? `错误: ${error}` : "请输入邮箱地址"}
accessibilityState={{ invalid: !!error }}
/>
性能优化技巧
防抖验证
避免频繁验证导致的性能问题:
import { debounce } from "lodash"
const useDebouncedValidation = () => {
const [email, setEmail] = useState("")
const [error, setError] = useState("")
const validateEmail = useCallback(
debounce((email: string) => {
if (!email) setError("邮箱不能为空")
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) setError("邮箱格式不正确")
else setError("")
}, 300),
[]
)
const handleEmailChange = (text: string) => {
setEmail(text)
validateEmail(text)
}
return { email, error, handleEmailChange }
}
记忆化验证函数
使用useMemo
和useCallback
优化验证性能:
const RegistrationForm = () => {
const [formData, setFormData] = useState(initialFormData)
const validationRules = useMemo(() => ({
email: [
{ validate: (v) => !!v, message: "邮箱不能为空" },
{ validate: (v) => /\S+@\S+\.\S+/.test(v), message: "邮箱格式不正确" }
],
password: [
{ validate: (v) => v.length >= 8, message: "密码至少8位" },
{ validate: (v) => /[A-Z]/.test(v), message: "必须包含大写字母" }
]
}), [])
const validateField = useCallback((name: keyof typeof formData, value: string) => {
const rules = validationRules[name]
if (!rules) return ""
for (const rule of rules) {
if (!rule.validate(value)) return rule.message
}
return ""
}, [validationRules])
}
测试策略
单元测试验证逻辑
// validators.test.ts
import { validators } from "./validators"
describe("validators", () => {
test("required validator", () => {
expect(validators.required("")).toBe("该字段不能为空")
expect(validators.required("test")).toBe("")
})
test("email validator", () => {
expect(validators.email("invalid")).toBe("邮箱格式不正确")
expect(validators.email("test@example.com")).toBe("")
})
test("minLength validator", () => {
expect(validators.minLength("123", 5)).toBe("至少5个字符")
expect(validators.minLength("12345", 5)).toBe("")
})
})
组件集成测试
// TextField.test.tsx
import { render, fireEvent } from "@testing-library/react-native"
import { TextField } from "./TextField"
describe("TextField validation", () => {
it("displays error message when status is error", () => {
const { getByText } = render(
<TextField status="error" helper="Invalid email" />
)
expect(getByText("Invalid email")).toBeTruthy()
})
it("updates error message dynamically", () => {
const { getByText, rerender } = render(
<TextField helper="Initial error" />
)
rerender(<TextField helper="Updated error" />)
expect(getByText("Updated error")).toBeTruthy()
})
})
常见问题与解决方案
问题1:验证状态不同步
症状:UI显示错误状态但实际验证已通过 解决方案:确保验证逻辑和状态更新在同一个事件循环中
// 错误做法
const handleChange = (text: string) => {
setEmail(text)
// 这里可能获取到旧的email值
setError(validateEmail(text))
}
// 正确做法
const handleChange = (text: string) => {
setEmail(text)
setError(validateEmail(text)) // 使用最新的text值
}
问题2:多次重复验证
症状:同一字段被多次验证导致性能问题 解决方案:使用防抖和验证缓存
const useCachedValidation = () => {
const [cache, setCache] = useState<Record<string, string>>({})
const validateWithCache = (value: string, validator: (v: string) => string) => {
if (cache[value] !== undefined) return cache[value]
const error = validator(value)
setCache(prev => ({ ...prev, [value]: error }))
return error
}
return { validateWithCache }
}
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考