react-form-field-hook
v1.0.2
Published
⚡️ A lightweight React hook for managing form field state.
Readme
react-form-field-hook
⚡️ 一个轻量级的 React Hook,用于管理表单字段状态。
English | 简体中文
✨ 特性
- 🎯 类型安全 - 完整的 TypeScript 支持
- 🚀 轻量级 - 无外部依赖,体积小
- 📝 灵活的验证 - 支持同步和异步验证
- 🔄 防抖验证 - 可配置的验证防抖
- 💪 丰富的状态 - 跟踪 touched、dirty、pristine、valid 等状态
- 🎭 多字段管理 - 使用
useFormFields管理多个字段 - 📦 开箱即用的验证器 - 内置常用验证规则
📦 安装
npm install react-form-field-hook
# 或
yarn add react-form-field-hook
# 或
pnpm add react-form-field-hook🚀 快速开始
基础用法
import { useFormField, validators } from 'react-form-field-hook';
import { Input, Button } from 'antd';
function MyForm() {
const emailField = useFormField({
initialValue: '',
rules: [validators.required(), validators.email()],
validateOnChange: true,
validateOnBlur: true,
});
const handleSubmit = async () => {
const isValid = await emailField.validate();
if (isValid) {
console.log('Email:', emailField.value);
}
};
return (
<div>
<Input
placeholder="输入您的邮箱"
{...emailField.getAntdInputProps()}
/>
{emailField.renderError()}
<Button onClick={handleSubmit}>提交</Button>
</div>
);
}异步验证(带防抖)
const usernameField = useFormField({
initialValue: '',
rules: [
validators.required(),
validators.minLength(3),
async (value) => {
const response = await fetch(`/api/check-username?username=${value}`);
const { available } = await response.json();
return available ? null : '用户名已被占用';
}
],
validateOnChange: true,
validateDebounce: 300, // 300ms 防抖
});
return (
<div>
<Input {...usernameField.getAntdInputProps()} />
{usernameField.validating && <span>验证中...</span>}
{usernameField.renderError()}
</div>
);管理多个字段
const { fields, validateAll, resetAll, getValues } = useFormFields({
username: {
initialValue: '',
rules: [validators.required(), validators.minLength(3)],
},
email: {
initialValue: '',
rules: [validators.required(), validators.email()],
},
password: {
initialValue: '',
rules: [validators.required(), validators.minLength(6)],
},
});
const handleSubmit = async () => {
const isValid = await validateAll();
if (isValid) {
const formData = getValues();
// 提交表单...
}
};
return (
<form>
<Input {...fields.username.getAntdInputProps()} />
{fields.username.renderError()}
<Input {...fields.email.getAntdInputProps()} />
{fields.email.renderError()}
<Input.Password {...fields.password.getAntdInputProps()} />
{fields.password.renderError()}
<Button onClick={handleSubmit}>提交</Button>
<Button onClick={resetAll}>重置</Button>
</form>
);📚 API
useFormField
const field = useFormField<T>(options: FieldOptions<T>);选项
| 选项 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| initialValue | T | - | 字段的初始值 |
| rules | ValidationRule<T>[] | [] | 验证规则数组 |
| validateOnChange | boolean | true | 值变化时是否验证 |
| validateOnBlur | boolean | true | 失去焦点时是否验证 |
| validateDebounce | number | 0 | 验证防抖延迟(毫秒) |
| disabled | boolean | false | 字段是否禁用 |
| onValueChange | (value: T) => void | - | 值变化时的回调 |
返回的字段对象
状态属性:
value: T- 当前字段值touched: boolean- 字段是否已触碰(聚焦后失焦)dirty: boolean- 值是否已从初始值修改pristine: boolean- 值是否未从初始值修改valid: boolean- 字段是否有效invalid: boolean- 字段是否无效error: string | null- 当前验证错误信息validating: boolean- 是否正在验证(异步验证)visited: boolean- 是否至少聚焦过一次focused: boolean- 是否当前聚焦disabled: boolean- 是否禁用
方法:
onChange(value: T)- 处理值变化onBlur()- 处理失焦事件onFocus()- 处理聚焦事件setValue(value: T)- 手动设置值reset()- 重置到初始状态validate()- 手动触发验证setError(error: string | null)- 手动设置错误setTouched(touched: boolean)- 标记为已触碰setDisabled(disabled: boolean)- 设置禁用状态setInitialValue(value: T)- 更新初始值getInputProps()- 获取输入组件的 propsgetHTMLInputProps()- 获取原生 HTML 输入元素的 propsgetAntdInputProps()- 获取 Ant Design 输入组件的 props(包含 status)renderError(className?: string)- 渲染错误信息
useFormFields
const { fields, form } = useFormFields(fieldsConfig);参数
fieldsConfig - 字段配置对象,key 为字段名,value 为 UseFormFieldOptions 配置
type FieldsConfig<T> = {
[K in keyof T]: UseFormFieldOptions<T[K]>;
};返回值
fields - 字段集合对象
包含所有配置的字段,每个字段都是完整的 useFormField 返回值(包含状态和方法)
fields.username.value
fields.username.error
fields.username.validate()
fields.username.getAntdInputProps()
// ... 所有 useFormField 的属性和方法form - 表单级操作对象
| 方法/属性 | 类型 | 描述 |
|---------|------|------|
| validateAll() | () => Promise<boolean> | 验证所有字段并返回是否全部有效 |
| resetAll() | () => void | 重置所有字段到初始状态 |
| getValues() | () => T | 获取所有字段的值作为对象 |
| setValues() | (values: Partial<T>) => void | 批量设置字段值 |
| setInitialValues() | (values: Partial<T>) => void | 批量设置初始值(用于编辑表单) |
| getErrors() | () => Partial<Record<keyof T, string \| null>> | 获取所有字段的错误信息 |
| setDisabled() | (disabled: boolean) => void | 设置所有字段的禁用状态 |
| isDirty | boolean | 是否有任何字段被修改 |
| isValid | boolean | 是否所有字段都有效 |
| isDisabled | boolean | 是否所有字段都被禁用 |
getValues()- 获取所有字段的值对象
内置验证器
所有验证器都返回一个验证规则函数:
// 必填验证
validators.required(message?: string)
// 邮箱格式
validators.email(message?: string)
// 最小长度
validators.minLength(min: number, message?: string)
// 最大长度
validators.maxLength(max: number, message?: string)
// 正则表达式
validators.pattern(regex: RegExp, message: string)
// 最小数值
validators.min(min: number, message?: string)
// 最大数值
validators.max(max: number, message?: string)
// URL 格式
validators.url(message?: string)
// 数字格式
validators.number(message?: string)
// 整数格式
validators.integer(message?: string)
// 匹配其他字段(如密码确认)
validators.matches(getOtherValue: () => T, message?: string)
// 值在允许列表中
validators.oneOf(allowedValues: T[], message?: string)
// 自定义验证(支持异步)
validators.validate(
validator: (value: T) => boolean | Promise<boolean>,
message: string
)
// 手机号格式
validators.phone(message?: string)自定义验证规则
// 同步验证
const customRule: ValidationRule<string> = (value) => {
if (!value.startsWith('prefix-')) {
return '值必须以 "prefix-" 开头';
}
return null;
};
// 异步验证
const asyncRule: ValidationRule<string> = async (value) => {
const response = await fetch(`/api/validate?value=${value}`);
const { valid } = await response.json();
return valid ? null : '验证失败';
};
const field = useFormField({
initialValue: '',
rules: [customRule, asyncRule],
});🎯 不同 UI 框架的集成方法
Ant Design / Neat Design
<Input {...field.getAntdInputProps()} />原生 HTML
<input {...field.getHTMLInputProps()} />其他组件库
<CustomInput {...field.getInputProps()} />🔧 高级用法
字段状态追踪
const field = useFormField({ initialValue: '' });
// 检查各种状态
console.log(field.touched); // 是否触碰
console.log(field.dirty); // 是否修改
console.log(field.pristine); // 是否原始
console.log(field.valid); // 是否有效
console.log(field.invalid); // 是否无效
console.log(field.visited); // 是否访问过
console.log(field.focused); // 是否聚焦密码确认示例
const passwordField = useFormField({
initialValue: '',
rules: [validators.required(), validators.minLength(6)],
});
const confirmField = useFormField({
initialValue: '',
rules: [
validators.required(),
validators.matches(
() => passwordField.value,
'两次密码输入不一致'
),
],
});动态更新初始值(编辑表单)
const userField = useFormField({ initialValue: '' });
// 加载数据后更新初始值
useEffect(() => {
async function loadUser() {
const user = await fetchUser(userId);
userField.setInitialValue(user.name);
}
loadUser();
}, [userId]);使用 useFormFields 管理整个表单
const { fields, form } = useFormFields({
username: {
initialValue: '',
rules: [validators.required(), validators.minLength(3)],
},
email: {
initialValue: '',
rules: [validators.required(), validators.email()],
},
age: {
initialValue: '',
rules: [validators.number(), validators.min(0)],
},
});
const handleSubmit = async () => {
if (await form.validateAll()) {
const formData = form.getValues();
await submitToAPI(formData);
}
};
const handleLoadData = async () => {
const data = await fetchUserData();
form.setInitialValues(data); // 批量设置初始值
};
// 检查表单状态
const canSubmit = form.isValid && form.isDirty && !form.isDisabled;
return (
<form>
<Input {...fields.username.getAntdInputProps()} />
{fields.username.renderError()}
<Input {...fields.email.getAntdInputProps()} />
{fields.email.renderError()}
<Input {...fields.age.getAntdInputProps()} />
{fields.age.renderError()}
<Button onClick={handleSubmit} disabled={!canSubmit}>
提交
</Button>
<Button onClick={() => form.resetAll()}>重置</Button>
</form>
);值转换(Transform)
const phoneField = useFormField({
initialValue: '',
transform: (value: string) => {
// 自动格式化为 123-456-7890
const digits = value.replace(/\D/g, '');
if (digits.length <= 3) return digits;
if (digits.length <= 6) return `${digits.slice(0, 3)}-${digits.slice(3)}`;
return `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6, 10)}`;
},
rules: [validators.pattern(/^\d{3}-\d{3}-\d{4}$/, '格式:123-456-7890')],
});自定义比较函数(compareWith)
// 忽略顺序的数组比较
const tagsField = useFormField({
initialValue: 'react,vue,angular',
compareWith: (a, b) => {
const arrA = a.split(',').sort();
const arrB = b.split(',').sort();
return JSON.stringify(arrA) === JSON.stringify(arrB);
},
});
// 输入 "vue,react,angular" 仍然是 pristine 状态条件验证
const [isPremium, setIsPremium] = useState(false);
const usernameField = useFormField({
initialValue: '',
rules: [
validators.required(),
validators.validate(
(value) => !isPremium || value.length >= 10,
'高级用户名至少需要 10 个字符'
),
],
});
// 当 isPremium 改变时重新验证
useEffect(() => {
usernameField.validate();
}, [isPremium]);🤝 贡献
欢迎贡献!请随时提交 Pull Request。
📄 许可证
MIT © leonwgc
