关于antd,Form.List的validateFields使用问题

4,291 阅读2分钟

根据antd的官方文档,validateFields接收一个NamePath类型的数组作为参数,格式如下:

(nameList?: NamePath[]) => Promise

举个简单的例子:

    <Form form={form}>
        <Form.Item name='a'></Form.Item>
        <Form.Item name='b'></Form.Item>
        <Form.Item name='c'></Form.Item>
    </Form>
    const [form] = Form.useForm();

    // 校验全部,由于nameList是可选参数,不传的情况下就是校验改表单下的全部
    form.validateFields().then(values => {})

    //校验a、b
    form.validateFields(['a','b']).then(values => {})

Form.List的校验问题

    <Form form={form}>
        <Form.Item name='a'></Form.Item>
        <Form.List name="list">
            {({name}, { add, remove }, { errors }) => (
                <>
                    <Form.Item name='b'></Form.Item>
                    <Form.Item name='c'></Form.Item>
                </>
            )}
        </Form.List>
    </Form>
    const [form] = Form.useForm();

类似上面这段伪代码,如果想要校验Form.List的第一个元素(['list', 0]),并且获取到对应的值,那又要如何实现呢?

form.validateFields(['list', 0]).then(values => {})
form.validateFields([['list', 0]]).then(values => {})

上面这两种写法都是有问题的,安装官方文档来理解,看起来第二种应该是可以的,但是其实这种写法只是校验到Form.List的rules,不会继续往下遍历了。难道需要把全部都列举出来吗,类似:

form.validateFields([['list', 0, 'a'], ['list', 0, 'b']]).then(values => {})

未免有点麻烦了,特别是当字段很多或者有更深层次嵌套的时候。从官方文档也没能找到更好的方式,但是本文接下来会提供一个方式,当然,这里要强调一下,目前基于antd(4.23.0),rc-field-form(1.27.2)来进行讲解的。

从antd的form表单源码可以看到,其实antd中的form是基于rc-field-form进行包装的,其中也包括useForm:

import { useForm as useRcForm } from 'rc-field-form';

然后我们再来看一下rc-field-form的代码,可以看到其中关于validateFields这个api,其实除了antd官方文档中的第一个参数nameList,还有第二个参数options:

private validateFields: InternalValidateFields = (
    nameList?: NamePath[],
    options?: ValidateOptions,
) => {
    // xxx
}

export interface ValidateOptions {
  triggerName?: string;
  validateMessages?: ValidateMessages;
  /**
   * Recursive validate. It will validate all the name path that contains the provided one.
   * e.g. ['a'] will validate ['a'] , ['a', 'b'] and ['a', 1].
   */
  recursive?: boolean;
}

从这个上面看起来,一年11个月前加的recursive,就是我们解决问题的关键。似乎我们可以这样写:

form.validateFields([['list', 0]], {recursive: true}).then(values => {})

但是,运行的效果并不理想,没有我们想象中的深入递归校验的效果,换一种写法呢:

form.validateFields(['list', 0], {recursive: true}).then(values => {})

校验效果有了,但是获取到的values有点奇怪。难道就没有完整的解决方案了吗,有的,你可以这样实现:

form.validateFields(['list', 0], {recursive: true}).then(() => {
    const values = form.getFieldsValue([['list', 0]]);
    console.log(values);
})

之所以这样,是因为validateFields的实现逻辑里面,recursive的时候:

if (options?.recursive && provideNameList) {
    const namePath = field.getNamePath();
    if (
        // nameList[i] === undefined 说明是以 nameList 开头的
        // ['name'] -> ['name','list']
        namePath.every((nameUnit, i) => nameList[i] === nameUnit || nameList[i] === undefined)
    ) {
        namePathList.push(namePath);
    }
}

所以,当recursive为true的时候,只支持['list', 0],如果是[['list', 0]]这种,那么是没办法进if的。

至于validateFields的values获取,是基于getFieldsValue实现的,getFieldsValue是支持并需要用到[['list', 0]]这种的,伪代码如下:

const returnPromise: Promise<Store | ValidateErrorEntity | string[]> = summaryPromise
    .then((): Promise<Store | string[]> => {
        if (this.lastValidatePromise === summaryPromise) {
            return Promise.resolve(this.getFieldsValue(namePathList));
        }
        return Promise.reject<string[]>([]);
    })

return returnPromise as Promise<Store>;

到了这一步,就把上面解决方案的实现逻辑给理清楚了!