- Published on
- 约 1727 字
Antd Form 校验(validateFields)失败提示outofdate
- Authors
- Name
- 小辉辉
背景
好多次想分享点文章,到最后又都放弃了,眼看着2025已经过去快半个月了,想想还是要压一压自己,那就发一篇最近排查了一个很久的问题吧。
情况是这样的,项目里有个表单编辑界面,用antd的form组件包裹来实现,借助form组件我们可以轻松实现对表单元素值的收集、赋值功能,除此之外,自带的校验用起来特别方便。
一切都是那么正常,直到有一次有业务找我说页面提交没有反应,但是控制台有输出提示是表单校验错误,但是页面上也没有看到相关的标记,我们看下控制台抛出的错误信息,没有看到errorFields的相关错误字段,但是有个outOfDate属性为true,这个属性之前见到过,基本就是代表在校验的时候有值改动了,antd为了确保校验的时候能够按最新的值来,所以会抛出这个outofdate错误的属性,表示当前值已经更新不是最新值了需要重新校验。
好,在了解了业务那边反馈的是只有他那个页面遇到了这个问题,接下来我们开始交代下整个编辑页面的处理流程,我们的编辑页面类似于用低代码的模式来实现,业务可以自行配置每个页面,按照需要拖入组件库,绑定相关事件还有页面数据,如此一来用户就能轻松实现页面数据的提交修改。
配置的实现讲完了,再来看看业务侧的,一般先是配置一个按钮,绑定按钮的点击事件,事件里面选择平台侧对外开放的提交(submit)API,就这么简单,submit接口其实就是方便业务自由组织逻辑。那submit里面的实现也很简单,主要有以下几个步骤:
- 调用用户前置提交事件
- 校验表单数据,这里使用antd form的validateFields方法
- 按照后段需要的要求转换表单收集返回的数据
- 调用接口完成页面新增修改操作
讲完了逻辑,目前就发现第一步validateFields方法就出错了,请看下面的分析步骤。
分析
我的第一反应是事事直接在控制台调用antd form的validateFields方法,考虑到业务提到了只有这个页面有问题,而且用户的前置提交事件也配置了相关自定义代码,为了更存粹的校验页面字段,很有必要直接调用该方法,最后就发现校验正常没有错误。
也就是因为这个步骤,让我前期排查陷入了误区,一开始就以为是业务的前置提交事件带来了错误,直到直接去除了这部分代码,还是校验错误才确认了不是这个原因。
接着就猜测是我们自己的前置保存逻辑有问题了,要不然为啥单独调用validateFields方法没有错误呢?这又是第二个误区,直到把相关代码仔仔细细排查了一遍也没发现相关端倪,而且若真的有问题,也很难解释为什么只有某个页面有问题了。
正当我卡住的时候,想想还是要回到问题的本质上面,我们从校验失败的提示信息入手吧。
搜了下form组件源码里outofdate,我们看到了是在一种情况下提示的,核心代码如下
if (this.lastPromise !== currentPromise){
return Promise.reject({
outOfDate: true
})
}
这段代码显而易见,就是说当前的校验实例和之前保存的校验实例不同一个的时候,就提示outofdate错误。那这个校验实例在每次调用validateFields方法的时候就会修改。
看到这里,那就简单了,我们只需在validateFields的地方打个断点看看是什么时候又调用了校验方法便可。
最终的结果就发现是项目在每次保存的时候有个业务逻辑会更新A字段附属的相关B字段的值,而这个更新用的是form的setFieldsValue方法,这个设值由于前后两次格式不一致,触发了B字段组件的update事件,项目在update事件里又会对当前值做一次修改转换从而再调用一次form的onChange事件,那这个onChange事件会直接触发antd form 组件的校验方法,可以理解为就是每次我们修改字段的时候,form字段会实时校验这个功能。
如此一来,便产生了两个校验实例,自然就提示outofdate错误了。
直到这里,细心的朋友就会发现,那为啥单独调用validateFields方法就不会有问题呢?照理说上面该走的逻辑还是会走啊,应该也有错误才对啊。
是的,我在打断点的时候就发现如果单独调用校验方法时,上面的方法的确会走,但是调用的时机不一样,具体流程表现为,字段B对应的👣update事件更新的时候,单独校验方法已经返回结果了,那后面由字段B带来的onChange事件引发的校验方法调用自然不会影响到上个结果了。
好好好,终于要讲到干货部分了,这一切的原因还是要归结于React组件的更新机制,结合上面的结果,我们可以推断出来,在点击事件里面触发的form组件更新(setFieldsValue)会优先于当前form组件的校验(validateFields)方法。如果不是在点击事件里调用校验方法,就不会有这个问题,还是会按照我们认为的顺序,先执行完当前校验方法,再执行组件更新。背后的原理涉及到react在处理事件时的异步、同步更新机制了。
总结
借助这次排查经历,让我意识到了解决问题最快的途径当然还是要从根源入手,如果能看到源码,结合错误分析对应的相关代码,配合调试,再结合大胆的猜测假设都能找到背后的根本原因。
回到上面的问题,最后我们定位的问题就是B字段对应的类型和实际数据的类型不一致,导致了每次保存的时候会触发update,解决方案也很简单,将组件类型切换为实际数据返回的类型,最终成功提交解决。