这篇文章上次修改于 652 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
今天又来了个新的需求,需要两个联动的输入框,而它们之间的“联动”在于,前者为王,后者不能大于前者的值。
举个简单的例子,卖商品的最低价格 <= 最高价格,最高价格不可能比最低价格要低吧。
要知道 Antd 4 和 3 的写法不太相同,4 主要使用的是 Hooks 式写法,而 3 还是 HOC 式为主。在这里我将实现一个简单的实例,作为记录和参考。源代码采用了 TypeScript,我也因此踩到了一个类型定义的坑。
编写基础表单
const [form] = Form.useForm();
type FormValue = {
min_price: number;
max_price: number;
};
const onFinish = (values: FormValue) => {
console.log("Submit: ", values);
}
<Form form={form} name="create" preserve={false}
initialValues={{ min_size: 1, max_size: 2}}
labelCol={{ span: 9 }} labelAlign="left"
validateTrigger="onBlur"
onFinish={onFinish}
>
<Form.Item name="min_price" label="最低价格" rules={[
{ required: true, message: "必须填写最低价格" },
{ type: "number", min: 1, max: 50000, message: "最低价格值错误" }
]}>
<InputNumber className="w-full" />
</Form.Item>
<Form.Item name="max_price" label="最高价格"
shouldUpdate={(prevValues, curValues) => prevValues.min_price !== curValues.min_price}
rules={[
{ required: true, message: "必须填写最高价格" }
]}
>
<InputNumber className="w-full" />
</Form.Item>
<Form.Item>
</Form>
既然「最低价格」是王,也就是说它默认最小为 1
就行了。而「最高价格」呢,肯定是需要看「最低价格」是多少。可以看到上面的代码里面,rules 参数传入的是一个固定的数组,这就蛋疼了,我该从哪里拿到 Antd 其他表单项的值呢?
如何引入其他表单项
查了下 Antd 的官方文档,以下是 Rules 的介绍:
Rule 支持接收 object 进行配置,也支持 function 来动态获取 form 的数据
但我发现一个很奇怪的问题,如果我用了 TypeScript,这里会出现类型错误。目前我的解决办法是直接上了万能 Any。今天写的时候竟然又没有报错了,很奇怪,不附上类型就行!
主要是函数里面的 FormInstance
很有可能是 Antd 所基于的 rc-form
库的定义,而 Antd 自有的同名定义又增加了它没有的属性,因此这里就会出现类型异常的情况。
不能将类型 (form: FormInstance) => { type: "number"; min: any; max: number; message: string; } 分配给类型 Rule。
不能将类型 (form: FormInstance) => { type: "number"; min: any; max: number; message: string; } 分配给类型 RuleRender。
参数 form 和 form 的类型不兼容。
类型 FormInstance缺少类型 FormInstance 中的以下属性: scrollToField, __INTERNAL__, getFieldInstance
<Form.Item
name="max_price"
label="最高价格"
shouldUpdate={(prevValues, curValues) =>
prevValues.min_price !== curValues.min_price
}
rules={[
{ required: true, message: "必须填写最高价格" },
// ! 错误写法 { min: form.getFieldValue("min_price") }
(form) => ({
type: "number",
min: form.getFieldValue("min_price"),
message: "最高价格值错误"
})
]}
>
<InputNumber className="w-full" />
</Form.Item>
使用函数写法,才能将正确的实例放入验证逻辑里面。这样写确实 OK 了,但是修改「最小价格」的时候并不会触发它的验证,用户一般也不知道它是否需要做修改。
手动触发验证
所以我们可以做个判断,在「最低价格」修改之后,手动触发一次表单的校验,出现问题的时候及时报错给用户。
<Form form={form} name="create" preserve={false}
initialValues={{ min_size: 1, max_size: 2}}
labelCol={{ span: 9 }} labelAlign="left" style={{maxWidth: "50em"}}
validateTrigger="onBlur"
onFinish={onFinish} onValuesChange={onValuesChange}
>
给 Form 加入 onValuesChange
回调,一旦有值被修改就会执行。
const onValuesChange = (values: any) => {
if(values.min_size){
form.validateFields(["max_size"]); // 校验 max_size
}
}
全篇源码可以从下面的 iframe 处访问:
至此,这个功能就成功实现完成!
已有 2 条评论
这类带联动校验的我还是习惯 Redux 一套带走;最主要是在 Redux 可以有一个入口完整调试表单的状态,至于觉得模板代码多什么的,可以自行写 factory 简化;react-hook-form 这类基于引用的方案也有考虑不过一来它迭代还是蛮猛的(v6 和 v7 的版本差有点大),可以视为整体的方案设计还不成熟,二来对大表单不友好,一旦表单拆分成组件,那么重名、优先管理等就是一团浆糊…
至于 Antd 的表单方案,再见。
@inori Redux 鄙人认识的太少,对我来说有些资深,不知什么环境下用会比较舒服。React-Hook-Form 之前实践过,功能比较全,V7 确实存在迭代后的迁移问题(之前试过),Antd 的表单方案是用的 rc-field-form 这个库,在复杂的环境下确实显得比较 Low,但它就是比较适合新手,也没什么好吐槽的地方了