现象与问题
如题,我们先来看看以下两个例子。
例子一:给 fieldKey 为 username 的输入框输入字母,将其转化成大写字母显示出来(通过 setFieldsValue)。实际操作的时候,我对另外一个 fieldKey 为 nickname 的输入框也进行了赋值,以验证仅对当前操作表单域赋值失败的问题。

输出结果证实:当通过 fieldKey 为 username 的输入框 onChange 回调方法给自身表单域设置值的时候是无效的,而不影响与之不同的表单域 nickname 的赋值。
例子二:跟例子一是差不多的代码,主要的区别是我们在 onChange 回调中尝试引入 setTimeout(func, 0)[1] 这种非同步的方式来调用 setFieldsValue。这么做的原因主要是,Antd-Form 组件本身是将一个个输入控件包裹一层再通过 setState 的机制来使输入控件变为受控组件。组件在 onChange 回调的时候一般是 Form 组件收集最新 value 的时机,所以一般在这个回调 Form 组件会根据情况进行 setState,再加上我们在 onChange 中又调用了一遍 setFieldsValue,所以我们这里可以猜测的结果是:Form 组件在调用了我们的 onChange 后,再调用自己内部的 setFieldsValue 来同步页面数据,这样的话我们执行的 setFieldsValue 就相当于被覆盖掉了。

输出结果证实:当我们通过 setTimeout(func, 0) 将 setFieldsValue 作为异步回调逻辑放到队列当中,那么可以确保我们的 setFieldsValue 会被有效的执行(不会发生覆盖),这样执行结果也跟我们预期的一致了。但这样的方式带来的坏处也显而易见,就是修改一次会触发两次 render。
注:此类问题对应我们公司 React 项目中自定义的 InputGroup 组件的一些联动设置值的问题。
阅读源码一探究竟
在上一部分中,我们对 Antd-Form 组件的行为进行猜测,但具体是不是这样,还需要从源码中得知,毕竟在我们公司项目中碰到此类问题还不少,觉得是时候深究一番了。
众所周知,Antd 中大部分组件的主要实现来自于 react-component 这个组(也是来自于阿里巴巴的前端工程师之手), Antd 将组件 api 进行定制以及优化后给到开发者进行开发。我们要找 Antd-Form 源码实则应该找的是 react-component/form 这个项目的源码。
首先,我们可以找到几个核心 api 的实现在 createBaseForm.js 这个文件里。近而,我们可以发现以下几个非常重要的函数:
1 | // 对应到我们例子一的话,这里的三个参数分别是:'username', 'onChange', event |
看了源代码后,发现与一开始猜测的八九不离十,只是官方很巧妙地用了 forceUpdate 避开了不必要生命周期调用这点没有预料到,好在 forceUpdate 与 setState 的合并行为是一致的。
最终,我们可以确定,在 Antd-From 组件表单域 onChange 回调中 setFieldsValue 修改自身表单域 value 无效的问题是因为用户自定义的 setFieldsValue 先于 createBaseForm.js 中同步状态值的 setFields调用,导致用户对于同个 setFields 的修改并不生效。
更优雅的解决方案
那有没有对于此类问题推荐的解决方案呢,其实是有的,如果我们认真阅读 Antd-From 组件文档,可以发现如下 api:
1 | 参数 | 说明 | 类型 |
例子三:使用 options.getValueFromEvent 不仅简化了代码,还确保了不会触发二次 render。

Reference
[1] “如果以0毫秒的超时时间来调用setTimeout(),那么指定的函数不会立刻执行。相反,会把它放到队列中,等到前面处于等待状态的事件处理程序全部执行完成后,再“立即”调用它。”
摘录来自: (美)David Flanagan. “JavaScript权威指南(原书第6版)”。 iBooks.
This is copyright.