You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
renderChildren(prefixCls: string){const{ children }=this.props;return[this.renderLabel(prefixCls),this.renderWrapper(prefixCls,this.renderValidateWrapper(prefixCls,children,this.renderHelp(prefixCls),this.renderExtra(prefixCls),),),];}
getFieldDecorator(name,fieldOption){// 拿到最新的 fields 数据constprops=this.getFieldProps(name,fieldOption);returnfieldElem=>{// We should put field in record if it is renderedthis.renderFields[name]=true;constfieldMeta=this.fieldsStore.getFieldMeta(name);constoriginalProps=fieldElem.props;
...
fieldMeta.originalProps=originalProps;fieldMeta.ref=fieldElem.ref;// 将数据传给 FormItem 中包裹的组件returnReact.cloneElement(fieldElem,{
...props,
...this.fieldsStore.getFieldValuePropValue(fieldMeta),});};},
flattenRegisteredFields(fields){constvalidFieldsName=this.getAllFieldsName();returnflattenFields(fields,path=>validFieldsName.indexOf(path)>=0,'You cannot set a form field before rendering a field associated with the value.',);}
这个方法会拿当前已有的 fieldsName,当切换到选项 B 的时候,Input 表单项还没渲染,导致 fieldsName 并没有记录这个值,所以就会走到这里的校验提示逻辑。更不会成功执行 setFields 操作了。
各大 Form 大揭秘 -- antd Form
Form
antd 的 Form 使用方式如下:
Form.create() 是一个高阶函数,它的源码如下:
create 函数直接调用的 rc-form 的 createDOMForm 方法,所以 antd 的 Form 的核心能力基本上都是在 rc-form 中实现的。
而 rc-form 中的 createDOMForm 中的 createDOMForm 直接调用了 createBaseForm,然后传递了一些 mixin 函数进去而已,所以我们直接看 createBaseForm 的源码,在 createBaseForm 中,直接是执行一个函数,也就是高阶函数实现的地方:
通过上面的源码可以看出,createBaseForm 渲染了 Form.create()(Example) 中传递的外部组件,并且把含有 一系列 API 的 Form 作为 props 传递给了外部组件。
Form 本身的逻辑并不复杂,只是通过传递 layout、labelCol、wrapperCol、onSubmit 等数据来控制整个 Form 的样式渲染以及表单的提交事件等。
FormItem
FormItem 的逻辑也主要是在渲染上,它的使用主要如下所示:
接收一个 children,并渲染。同时执行 renderLabel 和 renderWrapper,也就是渲染 ”label“ 和 ”内容“,这里引入了 antd 自己的栅格布局,每一个 FormItem 都是一个 Row,label 和 wrapper 是 Col。主要代码如下:
在 renderWrapper 的时候,同时会去拿每一个 Item 的校验状态,然后根据不同的状态更改 wrapper 的 className,从而控制样式如下样式:

为啥 FormItem 可以拿到状态呢?接下来就是 Form 核心功能的真正揭秘了
实例化 FieldsStore
前面提到执行 createBaseForm 的时候会返回一个经过 HOC 包装后的组件。这个组件在初始化的时候会执行一系列逻辑,从开始的 getInitialState 看起:
从代码可以看出在组件初始化的时候会实例化一个 FieldsStore 的类,这个类主要用来存储表单项的数据和校验状态、文案等。其中,FieldsStore 的 fields 属性主要存储每个表单项的实时状态,结构如下:
FieldsStore 中的 fieldsMeta 属性用来存储表单项的元数据信息,结构如下:
getFieldDecorator
当调用 form.getFieldDecorator 的时候,如下使用:
getFieldDecorator 是一个柯里化函数,用于装饰字段组件,首先传递一个表单项的配置,然后是传递一个组件。在执行 getFieldDecorator 的时候,会去执行 getFieldProps,这个函数主要用于装饰字段组件的 props,在这个函数中,会去设置 FieldsStore 的 fields 和 fieldsMeta 。需要注意的是,在获取 trigger(外部设置的收集子节点的事件,默认是 onChange) 事件的时候,会给事件绑定一个 onCollectValidate 或者 onCollect 回调。对应的代码请移步这里。这就实现了在触发组件的 onChange 的时候,就会去触发绑定的回调。
onCollectValidate 和 onCollect 都调用了 onCollectCommon,onCollectCommon 的代码如下:
在这个函数中,会去重新获取组件的值,并更新 fields 和 fieldsMeta。更新完之后,onCollectValidate 会将该 field 的 dirty 属性置为 true,并调用 validateFieldsInternal 对表单项做校验,在 validateFieldsInternal 中,实例化了一个 async-validator,并拿到 error 信息,更新表单项的校验状态到 fields 中。getFieldDecorator 拿到最新 fields 数据之后,将它作为 props 传递给 FormItem 中包裹的组件,代码如下:
如代码所示,getFieldDecorator 会去拿每次 change 后最新的 fields 数据(包括校验信息),然后将这些数据整合传递给被 FormItem 包裹的组件,这就是上文 FormItem 可以拿到表单项的校验状态的原因了。
setFieldsValue
createBaseForm 中还有一个一个 Api setFieldsValue,它的作用就是用户可以手动设置表单项的值,它里面调用 createBaseForm 的 setFields 方法,setFields 的代码如下:
将新设置的数据更新到 fields 中,然后执行 forceUpdate,强制更新,渲染最新的值。
小结
将上面的流程用时序图表示如下:
Form 踩到的一些坑
给未 render 的表单项设置值报错
🌰场景还原
有三个选项,其中,选择选项 b 的时候,显示一个 input 表单项,并手动设置其值,代码如下:
按照上述代码执行,会得到 Form 的 warning 提示:
Warning: You cannot set a form field before rendering a field associated with the value.
并且 Input 表单项正确设置值。
🔎原因诊断
在 setFieldsValue 的时候,会去调用 fieldsStore 的 flattenRegisteredFields 方法:
这个方法会拿当前已有的 fieldsName,当切换到选项 B 的时候,Input 表单项还没渲染,导致 fieldsName 并没有记录这个值,所以就会走到这里的校验提示逻辑。更不会成功执行 setFields 操作了。
💡如何解决
等 Input 组件渲染完成了再执行 setFieldsValue 操作,如下:
对 Form 的感受
整体来说,antd 的 Form 可以让开发者更加便捷,因为里面封装了一些逻辑,使得我们减少重复的劳动。但是也有一些缺点,如下:
The text was updated successfully, but these errors were encountered: