Better

Ethan的博客,欢迎访问交流

Ant Design Form 表单总结

Form 表单应该是 Ant Design 中最为复杂的组件了,在 4.x 版本中更是迎来一波重写,这里总结下碰到的问题及解决办法。

为什么需要

在前后端分离之前,原生的 form 标签是一个非常常用的,因为我们需要配合 action 属性达到提交数据的目的。

后来我们很少手写 form 标签了,但 UI 框架通常会提供 Form 组件给我们使用,但通常比原生的 form 功能强大,不仅仅提供数据录入,更是集成强大的表单校验功能。

Ant Design 中更是提供 component 属性,当设置为 false 时,直接不创建原生 form 节点

Ant Design 将 Form 的使用场景定义为

  • 用于创建一个实体或收集信息。
  • 需要对输入的数据类型进行校验时。

FormInstance

在 4.x 之前,通过 HOC 的方式从 props 中获取 form 实例。在 4.x 后,支持通过 hook 方式获取,两个步骤

  1. 使用 useForm 获取实例
  2. 通过 form 属性传递给 Form 组件

在 Class 组件中,也可以通过 ref 获取 Form 实例

表单绑定

Form.Item 只会对它的直接子元素绑定表单功能,如果控件前后还有一些文案或样式装点,或者一个表单项内有多个控件,你可以使用内嵌的 Form.Item 完成。你可以给 Form.Item 自定义 style 进行内联布局,或者添加 noStyle 作为纯粹的无样式绑定组件。这时候外层的 Form.Item 无需指定 name 属性,仅作布局使用

自定义数据录入组件

自定义数据能不能托管给 Form 呢。答案是可以的。只要保证下面两条即可

  • 提供受控属性 value 或其它与 valuePropName 的值同名的属性。
  • 提供 onChange 事件或 trigger 的值同名的事件。

按钮禁用

很多时候在表单不通过时候,我们希望直接禁用提交按钮,可以使用如下方式

<Form.Item shouldUpdate={true}>
    {() => (
        <Button
        type="primary"
        htmlType="submit"
        disabled={
            !form.isFieldsTouched(true) ||
            form.getFieldsError().filter(({ errors }) => errors.length).length
        }
        >
        Log in
        </Button>
    )}
</Form.Item>

如果表单初始值就是不通过,则希望一开始就禁用,可以手动触发依次强渲染

const [, forceUpdate] = useState();
useEffect(() => {
    forceUpdate({});
}, []);

手动触发一次校验呢?

shouldUpdate、dependencies

初次使用时,容易对这两个字段产生误解。

  • dependencies 只是针对校验而言,也就是何时重新触发校验
  • shouldUpdate 是 Ant Design 一个优化手段,用来达到局部更新表单的目的

大部分场景下,你不需要管 shouldUpdate 属性,在某些特定场景,比如

  • 修改某个字段值后出现新的字段选项
  • 纯粹希望表单任意变化都对某一个区域进行渲染,比如上面的按钮禁用

当我们自定义封装一个 Form 表单时,他将来的使用场景可能是不确定的,可能使用在普通的面板组件中,可能使用 Modal 中。很多时候我们会需要拿到 form 对象(因为 form 上有很多的 API 会很方便),直接类比与 Ant Design Form 表单,通常我们也需要提供两种方式获取

  • 传递 form props 方式
  • ref 方式

如果毕竟是业务组件,你也可以 2 选 1 实现即可。

自定义 validator

Ant Design 默认提供了很多验证器,比如

  • enum
  • len
  • max
  • min
  • pattern
  • required
  • whitespace:如果字段仅包含空格则校验不通过

但有时候我们需要自定义自己的验证器,rules 数组中可以直接定义包含 validator 属性的对象,也可以接受函数,函数返回一个包含 validator 属性的对象即可,简单例子如下

({ getFieldValue }) => ({
    validator(rule, value) {
        if (!value || getFieldValue('password') === value) {
        return Promise.resolve();
        }
        return Promise.reject('The two passwords that you entered do not match!');
    },
}),

自定义校验

你可以不通过 Form 自己定义校验的时机和内容。Form.Item 提供了 validateStatus help hasFeedback 等属性

  • validateStatus: 校验状态,可选 'success', 'warning', 'error', 'validating'。
  • hasFeedback:用于给输入框添加反馈图标。
  • help:设置校验文案。

注意点

min 和 max 表单验证

当你使用 min 配合 max 对数据进行范围校验时,需要同时指定 type 为 number,否则会不生效,例子如下

rules={[{ type: 'number', min: 0, max: 99 }]}

正确理解 onFieldsChange 事件

通过 onFieldsChange 和 fields,可以把表单的数据存储到上层组件

一开始无法区分 onValuesChange 和 onFieldsChange,区别如下

  • onFieldsChange:字段更新时触发回调事件,可能触发的原因比 onValuesChange 多很多,还包括 touchedvalidatingerrors
  • onValuesChange:仅表示字段值更新时的回调,注意:触发该事件时,form 管理的数据还没有更新,因为在同一个 tick 中进行 validateFields 会不符合预期

normalize 作用

这个举个使用的场景,不同于 InputNumber 组件,内部已经将输入值转成 number 格式,如果你只是使用普通 Input 组件,即便你将 type 设置为 number,得到的值依旧时 string 值,这时候你将 string 放入 form 的话,就可能导致 min 或 max 等校验器失败。这时候就可以通过该属性,将值在放入 form 之前,转成 number 类型。

更多进阶

动态增减表单项

通过 Form.List 的 render props 实现。还可以实现嵌套字段,比如 user.name 等。

Form.Provider

类比 Form 管理 Form.Item,Form.Provider 可以管理多个 Form,Form 可以通过设置 name 属性来区分多个表单,来实现表单联动的效果。

使用 form.submit 还是 htmlType="submit"

  • 如果提交按钮在 Form 之外则使用 form.submit
  • 否则推荐使用 htmlType="submit"

在 Form 触发 submit 后,会调用触发 Form.Provider 上的 onFormFinish 事件,这是管理多表单联动核心。同时当子表单字段更新时触发是会触发 onFormChange 事件

扩展

判断是不是一个数

在项目直接使用 isNaN 是会被 eslint 检测报错。其实这个函数在 Number 对象上也有,可直接使用 Number.isNaN

useResetFormOnCloseModal

const useResetFormOnCloseModal = ({ form, visible }) => {
  const prevVisibleRef = useRef();
  useEffect(() => {
    prevVisibleRef.current = visible;
  }, [visible]);
  const prevVisible = prevVisibleRef.current;

  useEffect(() => {
    if (!visible && prevVisible) {
      form.resetFields();
    }
  }, [visible]);
};


留言