React动态表单渲染及提交/失焦校验实现求助
React动态表单完整实现方案
你已经完成了基础的表单配置渲染和输入值收集,剩下的校验相关功能可以按照下面的步骤补全,代码完全兼容现有配置格式,后续配置变更不需要修改核心逻辑。
核心实现思路
- 单独维护表单错误状态,和输入值状态分开管理
- 抽离通用单字段校验函数,提交校验、失焦校验都复用这一个函数,避免重复代码
- 校验规则完全读取配置里的
validation字段,支持扩展新规则无需改动渲染逻辑 - 给输入框绑定
onBlur事件,失焦时触发对应字段的校验
完整实现代码
import { useState } from 'react'; const DYNAMIC_FORMS = [ { texts: { header: 'Name Section' }, inputs: [ { label: 'Full Name', type: 'text', name: 'name', validation: { required: true, minLength: 4 }, error: 'Name is required' } ] }, { texts: { header: 'Password Section' }, inputs: [ { label: 'Password', type: 'password', name: 'password', validation: { required: true, minLength: 6, maxLength: 10 }, error: 'Password is required' }, { label: 'Pincode', type: 'number', name: 'pincode', validation: { required: true, minLength: 6, maxLength: 6 }, error: 'Invalid Pincode' } ] }, { texts: { header: 'Address Section' }, inputs: [ { label: 'Address Line 1', type: 'text', name: 'addressLine1', validation: { required: true }, error: 'Invalid Address' }, { label: 'Address Line 2', type: 'text', name: 'addressLine2', validation: { required: false } }, { label: 'Zipcode', type: 'number', name: 'zipcode', validation: { required: true, minLength: 6, maxLength: 7 }, error: 'Invalid Zipcode' } ] }, { texts: { header: 'Thankyou for filling out the form' } } ]; export default function App() { // 存储表单输入值 const [formValues, setFormValues] = useState({}); // 存储表单校验错误,key为字段name,value为错误文案 const [formErrors, setFormErrors] = useState({}); /** * 通用单字段校验函数 * @param {object} fieldConfig 字段配置项 * @param {string|number} value 字段当前值 * @returns {string|null} 有错误返回错误文案,无错误返回null */ const validateField = (fieldConfig, value) => { const { validation, error } = fieldConfig; // 必填校验 if (validation.required) { if (!value || String(value).trim() === '') { return error; } } // 最小长度校验 if (validation.minLength) { if (String(value).length < validation.minLength) { return error; } } // 最大长度校验 if (validation.maxLength) { if (String(value).length > validation.maxLength) { return error; } } // 后续扩展正则、自定义校验规则,直接在这里加判断即可 return null; }; // 输入框值变化回调 const onInputChange = ({ target: { name, value } }) => { setFormValues((prev) => ({ ...prev, [name]: value })); // 输入时如果该字段已有错误,实时清除,优化体验 if (formErrors[name]) { setFormErrors(prev => { const newErrors = {...prev}; delete newErrors[name]; return newErrors; }) } }; // 输入框失焦回调,触发单字段校验 const onInputBlur = (fieldConfig) => { const fieldValue = formValues[fieldConfig.name] || ''; const errorMsg = validateField(fieldConfig, fieldValue); setFormErrors(prev => ({ ...prev, [fieldConfig.name]: errorMsg })) }; // 表单提交回调 const onSubmit = (e) => { e.preventDefault(); const allErrors = {}; // 遍历所有配置字段做全量校验 DYNAMIC_FORMS.forEach(section => { if (!section.inputs) return; section.inputs.forEach(field => { const fieldValue = formValues[field.name] || ''; const errorMsg = validateField(field, fieldValue); if (errorMsg) { allErrors[field.name] = errorMsg; } }) }) setFormErrors(allErrors); // 无错误时执行提交逻辑 if (Object.keys(allErrors).length === 0) { console.log('表单校验通过,提交数据:', formValues); alert('提交成功!'); } }; return ( <div style={{ maxWidth: 500, margin: '40px auto' }}> <form autoComplete="off" onSubmit={onSubmit}> {DYNAMIC_FORMS.map((section) => ( <div key={section.texts.header} style={{ marginBottom: 24 }}> <h3>{section.texts.header}</h3> {section.inputs && ( <> {section.inputs.map((field) => { const { label, name, type } = field; const fieldError = formErrors[name]; return ( <div key={name} style={{ marginBottom: 16 }}> <label style={{ display: 'block', marginBottom: 4 }}> {label} </label> <input type={type} name={name} value={formValues[name] || ''} onChange={onInputChange} onBlur={() => onInputBlur(field)} style={{ width: '100%', padding: '8px 12px', border: fieldError ? '1px solid #f5222d' : '1px solid #d9d9d9', borderRadius: 4, outline: 'none' }} /> {/* 错误提示渲染 */} {fieldError && ( <div style={{ color: '#f5222d', fontSize: 12, marginTop: 4 }}> {fieldError} </div> )} </div> ); })} </> )} </div> ))} <button type="submit" style={{ padding: '8px 24px', background: '#1677ff', color: '#fff', border: 'none', borderRadius: 4, cursor: 'pointer' }} > Submit </button> </form> </div> ); }
关键逻辑说明
- 移除了直接展开
validation到input标签的写法:原代码里的{...validation}会把required、minLength这类原生属性绑定到输入框,触发浏览器默认校验弹窗,和自定义JS校验逻辑冲突 - 通用校验函数做了逻辑复用:提交时的全量校验、失焦时的单字段校验都调用同一个
validateField方法,后续调整校验规则只需要修改这一处代码 - 错误状态独立存储:用单独的
formErrors状态存每个字段的错误信息,渲染时按字段名匹配对应提示即可 - 输入框改为完全受控组件:给input绑定了对应state的value,避免输入值和状态不同步的问题
- 额外优化了交互体验:用户输入内容时如果该字段已有错误提示,会实时清除,不需要等再次失焦才更新提示
- 扩展成本极低:后续如果要新增正则校验、自定义校验规则,只需要在
validateField里加对应判断,同时在表单配置的validation字段加规则参数即可,不需要调整渲染层代码
内容的提问来源于stack exchange,提问作者myN00bjourney
相关产品推荐
相关产品推荐

