React如何将表单从提交时全量验证改为单字段失焦即时验证
修改步骤与代码实现
1. 改造useForm Hook
新增失焦校验方法,优化错误判断逻辑,完整修改后的代码如下:
import { useState, useEffect } from 'react'; const useForm = (callback, validate, post) => { const [values, setValues] = useState(post || {}); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); // 优化错误为空的判断逻辑,兼容undefined的错误项 useEffect(() => { const hasValidErrors = Object.values(errors).some(err => err !== undefined); if (!hasValidErrors && isSubmitting) { callback(); } }, [errors, isSubmitting]); const handleSubmit = (event) => { if (event) event.preventDefault(); setErrors(validate(values)); setIsSubmitting(true); }; const handleInputChange = (event) => { event.persist(); setValues(values => ({ ...values, [event.target.name]: event.target.value })); }; // 新增失焦校验方法,仅校验当前失焦的字段 const handleBlur = (event) => { event.persist(); const fieldName = event.target.name; const fieldValue = event.target.value; // 用当前输入的最新值构造临时values,避免state异步更新导致的校验不准问题 const currentValues = { ...values, [fieldName]: fieldValue }; // 全量调用校验逻辑,仅提取当前失焦字段的错误 const allErrors = validate(currentValues); const currentFieldError = allErrors[fieldName]; // 合并到现有errors,仅更新当前字段的错误项,保留其他字段的错误状态 setErrors(prevErrors => ({ ...prevErrors, [fieldName]: currentFieldError || undefined })); }; return { handleInputChange, handleSubmit, handleBlur, // 导出失焦处理方法 values, errors, } }; export default useForm;
2. 无需修改validate校验函数
现有校验逻辑全部为单字段独立校验,可直接复用,无需调整。
3. 改造CreateForm表单组件
从useForm中解构出handleBlur方法,给所有输入控件绑定失焦事件,修改后的关键代码如下:
import { useHistory } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import useForm from '../hooks/useForm'; import { addPost } from '../actions'; import validate from '../helpers/validate'; const CreateForm = () => { const dispatch = useDispatch(); const history = useHistory(); // 新增解构handleBlur const { values, handleInputChange, handleSubmit, handleBlur, errors } = useForm(submit, validate) const { title, body, author, number, email } = values; function submit() { const post = values; dispatch(addPost(post)) history.push('/'); } return ( <div className="create"> <h2>Add a new blog</h2> <form onSubmit={handleSubmit} noValidate> <label>Blog title:</label> <input type="text" required name="title" value={title || ""} onChange={handleInputChange} onBlur={handleBlur} {/* 新增失焦绑定 */} className={errors.title ? 'red-border' : ""} /> {errors.title && (<p className="danger">{errors.title}</p>)} <label>Blog body:</label> <textarea required name="body" value={body || ""} onChange={handleInputChange} onBlur={handleBlur} {/* 新增失焦绑定 */} className={errors.body ? 'red-border' : ""} /> {errors.body && ( <p className="danger">{errors.body}</p> )} <label>Author:</label> <input type="text" required name="author" value={author || ""} onChange={handleInputChange} onBlur={handleBlur} {/* 新增失焦绑定 */} className={errors.author ? 'red-border' : ""} /> {errors.author && ( <p className="danger">{errors.author}</p> )} <label>Number:</label> <input type="number" required name="number" value={number || ""} onChange={handleInputChange} onBlur={handleBlur} {/* 新增失焦绑定 */} className={errors.number ? 'red-border' : ""} /> {errors.number && ( <p className="danger">{errors.number}</p> )} <label>Email:</label> <input type="text" required name="email" value={email || ""} onChange={handleInputChange} onBlur={handleBlur} {/* 新增失焦绑定 */} className={errors.email ? 'red-border' : ""} /> {errors.email && ( <p className="danger">{errors.email}</p> )} <button>Save</button> </form> </div> ); } export default CreateForm;
逻辑说明
- 失焦触发时,仅校验当前聚焦的输入字段,仅更新该字段的错误状态,其他未交互字段的错误保持隐藏
- 提交按钮点击时,仍执行全量字段校验,所有未通过的字段错误会全部展示,兼容未交互字段的校验需求
- 输入过程中不会触发校验,只有失焦或提交时才会执行校验,符合预期的交互逻辑
内容的提问来源于stack exchange,提问作者Monika
相关产品推荐
相关产品推荐

