今天又来了个新的需求,需要两个联动的输入框,而它们之间的“联动”在于,前者为王,后者不能大于前者的值。

举个简单的例子,卖商品的最低价格 <= 最高价格,最高价格不可能比最低价格要低吧。

要知道 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<any> 缺少类型 FormInstance<any> 中的以下属性: 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 处访问:

至此,这个功能就成功实现完成!