在现代 Web 应用程序中,表单处理是一个常见的需求。本文将展示如何仅使用 React 实现强大的表单验证,而无需引入额外的第三方库。
概述
我们将创建一个自定义的表单管理解决方案,涵盖以下主要功能:
- 通过
onSubmit
回调处理表单提交。 - 对单个输入进行前端验证。
- 在表单提交时进行验证,而不是在失去焦点时。
- 提供重置表单的功能。
工作原理
我们利用 React 的上下文(Context)API 来管理表单状态,并确保所有输入组件都能访问和更新这些状态。具体步骤如下:
- 创建一个表单上下文,用于保存所有输入的状态和验证逻辑。
- 当输入组件加载时,它们会向表单上下文注册自己,并传递必要的信息。
- 输入值发生变化时,更新表单上下文中的状态。
- 提交表单时,运行所有已注册的验证器,并根据结果设置错误消息。
下图展示了不同组件之间的交互:
实现细节
表单状态结构
表单状态需要存储以下三类信息:
- 用户输入的数据。
- 每个字段的验证规则。
- 特定字段的错误消息。
我们可以使用一个对象来表示这些信息:
const FORM_STATE = { data: {}, validators: {}, errors: {} };
每个输入必须有一个唯一的名称属性,以便在状态结构中作为键使用。
表单上下文
为了将表单状态和方法注入到各个输入组件中,我们使用了上下文提供者模式。这样可以确保所有子组件都可以轻松访问表单状态。
此外,我们还创建了一个高阶组件(HOC),用于封装上下文订阅逻辑,从而使输入组件保持纯粹的 UI 层。
表单方法
接下来,我们将详细介绍表单上下文的主要行为。
注册与注销
每当输入组件加载时,它会在表单上下文中注册自己,并复制其验证规则。卸载时,清除相关数据和验证规则。
const registerInput = ({ name, validators }) => { setFormState(state => ({ ...state, validators: { ...state.validators, [name]: validators || [] }, errors: { ...state.errors, [name]: [] } })); return () => { setFormState(state => { const { data, errors, validators: currentValidators } = { ...state }; delete data[name]; delete errors[name]; delete currentValidators[name]; return { data, errors, validators: currentValidators }; }); }; };
控制输入数据
对于受控输入,我们通过 onChange
事件更新表单上下文中的数据,并清除任何现有错误。
const setFieldValue = (name, value) => { setFormState(state => ({ ...state, data: { ...state.data, [name]: value }, errors: { ...state.errors, [name]: [] } })); };
提交与验证
当用户点击提交按钮时,系统会遍历所有验证器,并检查是否有任何错误。如果有错误,则更新错误状态;否则,调用 onSubmit
回调。
const validate = () => { setFormState(state => ({ ...state, errors: {} })); if (isEmpty(validators)) { return true; } const formErrors = Object.entries(validators).reduce((errors, [name, validators]) => { const messages = validators.reduce((result, validator) => { const value = formState.data[name]; const err = validator(value, formState.data); return [...result, ...err]; }, []); if (messages.length > 0) { errors[name] = messages; } return errors; }, {}); if (isEmpty(formErrors)) { return true; } setFormState(state => ({ ...state, errors: formErrors })); return false; };
请注意,我们一次性收集所有错误,以避免用户在修复多个错误时反复提交表单。
高级特性:包装输入组件
为了简化输入组件的开发,我们创建了一个高阶组件(HOC),用于自动处理输入注册、错误显示等功能。
import React, { useContext, useEffect } from 'react'; import PropTypes from 'prop-types'; import { FormContext } from './index'; const withForm = InputCompOnent=> { const WrappedWithForm = props => { const { errors, data, setFieldValue, registerInput } = useContext(FormContext); useEffect(() => registerInput({ name: props.name, validators: props.validators }), []); const OnChange= val => { setFieldValue(props.name, val); if (props.onChange) { props.onChange(val); } }; const inputValue = data[props.name]; const inputErrors = errors[props.name] || []; return ; }; WrappedWithForm.propTypes = { name: PropTypes.string.isRequired, validators: PropTypes.arrayOf(PropTypes.func) }; return WrappedWithForm; }; export default withForm;
完整示例:注册表单
最后,让我们看看一个完整的注册表单示例:
console.log(data)}>
我们添加了一些简单的验证器,例如必填字段验证:
const requiredValidator = val => { if (!val) { return ["This field is required"]; } return []; };
您可以尝试点击提交和重置按钮,观察其工作原理。
感谢阅读!希望这篇文章能帮助您更好地理解如何在 React 中处理表单验证。如果您有任何问题或建议,请随时留言。
行动中的形式