在 React 中,表单验证是一个很常见的问题,通常我们需要验证表单中输入的内容是否符合特定的规则和要求。但是,在动态表单中,由于表单组件的数量和属性都是动态生成的,这就给表单验证带来了一定的挑战。本文将介绍解决动态表单验证问题的最佳实践。
动态表单验证的挑战
在 React 中,通常我们会使用 state
或 props
来维护表单组件的状态和属性,验证也应该基于这些状态和属性来进行。但是,在动态表单中,表单组件的数量和属性都是动态变化的,如果我们只维护一个单一的验证状态,就很难应对各种复杂的验证场景,比如:
- 每个表单项都有不同的验证规则和错误提示;
- 表单项之间有联动验证,比如 A 输入框输入的值必须小于 B 输入框输入的值;
- 某些表单项的验证依赖于其他表单项的值,比如一个下拉菜单的选项必须是某个数组中的元素,而这个数组是通过 AJAX 请求获取的。
这些场景都要求我们使用更灵活和多元化的验证方案来应对动态表单验证的挑战。
最佳实践
- 表单项的状态与验证规则分离
动态表单验证的第一步是要将表单项的状态和验证规则分离开来。我们可以将表单项的状态存储在 state
或 props
中,而将验证规则和错误提示信息存储在一个独立的对象中。这个对象的键名对应表单项的名称,键值是一个对象,包含该表单项的验证规则和错误提示信息。示例代码如下:
// javascriptcn.com 代码示例 import React, { Component } from 'react'; const validateRules = { username: { required: true, pattern: /^[a-zA-Z0-9_-]{4,16}$/, message: '用户名必须由4-16位字母、数字、横线或下划线组成', }, password: { required: true, message: '密码不能为空', }, confirmPassword: { required: true, validator: (value, form) => value === form.password, message: '两次密码输入不一致', }, };
- 针对每个表单项分别验证
动态表单验证的第二步是要针对每个表单项分别进行验证。我们可以通过遍历表单项的名称,检查当前表单项的值是否符合对应的验证规则来进行验证。如果出现错误,则将错误信息存储到一个单独的对象中,键名为表单项的名称,键值为错误信息。示例代码如下:
// javascriptcn.com 代码示例 import React, { Component } from 'react'; const validateRules = { // ... }; class RegistrationForm extends Component { state = { username: '', password: '', confirmPassword: '', errors: {}, }; handleSubmit = event => { event.preventDefault(); const { errors, ...formData } = this.state; const validationErrors = {}; Object.keys(formData).forEach(fieldName => { const value = formData[fieldName]; const fieldRules = validateRules[fieldName]; const fieldError = []; if (fieldRules.required && !value) { fieldError.push(fieldRules.message || '此项不能为空'); } if (fieldRules.pattern && !fieldRules.pattern.test(value)) { fieldError.push(fieldRules.message || '此项格式错误'); } if (fieldRules.validator && typeof fieldRules.validator === 'function') { if (!fieldRules.validator(value, formData)) { fieldError.push(fieldRules.message || '此项验证失败'); } } if (fieldError.length) { validationErrors[fieldName] = fieldError; } }); if (Object.keys(validationErrors).length) { this.setState({ errors: validationErrors }); } else { // 表单验证通过,继续提交 } }; handleChange = event => { const { name, value } = event.target; this.setState({ [name]: value, }); }; render() { const { username, password, confirmPassword, errors } = this.state; return ( <form onSubmit={this.handleSubmit}> <div> <label htmlFor="username">用户名:</label> <input type="text" id="username" name="username" value={username} onChange={this.handleChange} /> {errors.username && errors.username.map((message, index) => <div key={index}>{message}</div>)} </div> <div> <label htmlFor="password">密码:</label> <input type="password" id="password" name="password" value={password} onChange={this.handleChange} /> {errors.password && errors.password.map((message, index) => <div key={index}>{message}</div>)} </div> <div> <label htmlFor="confirmPassword">确认密码:</label> <input type="password" id="confirmPassword" name="confirmPassword" value={confirmPassword} onChange={this.handleChange} /> {errors.confirmPassword && errors.confirmPassword.map((message, index) => <div key={index}>{message}</div>)} </div> <div> <button type="submit">注册</button> </div> </form> ); } }
- 表单项之间的联动验证
动态表单验证的第三步是要解决表单项之间的联动验证问题。这需要我们在每个表单项的验证规则中增加一个额外的可选属性 validator
,其值为一个回调函数,接受当前表单项的值和整个表单的值作为参数,并返回验证结果的布尔值。示例代码如下:
// javascriptcn.com 代码示例 import React, { Component } from 'react'; const validateRules = { // ... confirmPassword: { required: true, validator: (value, { password }) => value === password, message: '两次密码输入不一致', }, }; class RegistrationForm extends Component { state = { username: '', password: '', confirmPassword: '', errors: {}, }; // ... render() { const { username, password, confirmPassword, errors } = this.state; return ( <form onSubmit={this.handleSubmit}> <div> <label htmlFor="username">用户名:</label> <input type="text" id="username" name="username" value={username} onChange={this.handleChange} /> {errors.username && errors.username.map((message, index) => <div key={index}>{message}</div>)} </div> <div> <label htmlFor="password">密码:</label> <input type="password" id="password" name="password" value={password} onChange={this.handleChange} /> {errors.password && errors.password.map((message, index) => <div key={index}>{message}</div>)} </div> <div> <label htmlFor="confirmPassword">确认密码:</label> <input type="password" id="confirmPassword" name="confirmPassword" value={confirmPassword} onChange={this.handleChange} /> {errors.confirmPassword && errors.confirmPassword.map((message, index) => <div key={index}>{message}</div>)} </div> <div> <button type="submit">注册</button> </div> </form> ); } }
- 表单项的异步验证
动态表单验证的第四步是要支持表单项的异步验证。这需要我们在表单项的验证规则中增加一个额外的可选属性 asyncValidator
,其值为一个异步函数,接受当前表单项的值和整个表单的值作为参数,并返回验证结果的布尔值或者 Promise 对象。示例代码如下:
// javascriptcn.com 代码示例 import React, { Component } from 'react'; const validateRules = { // ... email: { required: true, asyncValidator: (value, { username }) => new Promise(resolve => setTimeout(() => { resolve(value !== `${username}@example.com`); }, 2000) ), message: '此邮箱已被占用', }, }; class RegistrationForm extends Component { state = { username: '', email: '', errors: {}, }; // ... render() { const { username, email, errors } = this.state; return ( <form onSubmit={this.handleSubmit}> <div> <label htmlFor="username">用户名:</label> <input type="text" id="username" name="username" value={username} onChange={this.handleChange} /> {errors.username && errors.username.map((message, index) => <div key={index}>{message}</div>)} </div> <div> <label htmlFor="email">邮箱:</label> <input type="email" id="email" name="email" value={email} onChange={this.handleChange} /> {errors.email && errors.email.map((message, index) => <div key={index}>{message}</div>)} </div> <div> <button type="submit">注册</button> </div> </form> ); } }
总结
动态表单验证是 React 中一个常见的问题,但也是一个比较复杂的问题。本文介绍了解决动态表单验证的最佳实践,包括将表单项的状态与验证规则分离、针对每个表单项分别验证、解决表单项之间的联动验证问题,以及支持表单项的异步验证等。通过这些实践,我们可以更好地应对各种复杂的动态表单验证场景,为用户提供更好的用户体验。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65316ee77d4982a6eb321f13