Ignite表单验证:输入验证与错误提示机制

Ignite表单验证:输入验证与错误提示机制

【免费下载链接】ignite Infinite Red's battle-tested React Native project boilerplate, along with a CLI, component/model generators, and more! 【免费下载链接】ignite 项目地址: https://gitcode.com/GitHub_Trending/ig/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 }
}

表单验证架构

对于复杂表单,建议使用分层架构:

mermaid

国际化与可访问性

多语言错误消息

利用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 }
}

记忆化验证函数

使用useMemouseCallback优化验证性能:

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 }
}

【免费下载链接】ignite Infinite Red's battle-tested React Native project boilerplate, along with a CLI, component/model generators, and more! 【免费下载链接】ignite 项目地址: https://gitcode.com/GitHub_Trending/ig/ignite

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值