AntD-Form 是基于 rc-form 来做了一层封装,我们看看它提供了哪些额外的功能
先来看看 Form
- 首先按照惯例获取
SizeContext
,ConfigContext
,主要获取Size,getPrefixCls,direction,form.requiredMark. - 然后依次来获取 className,mergedRequiredMark.
- 创建
formInstance
,这个是在rc.useForm
封装了一层,添加了scrollToField(),getFieldInstance(),getFieldInstance()
,这个是获取 Field 的 ReactElement. FormContext
这个跟 rc 的FormContext
不一样,虽然名字一样(很容易让人误导),这个主要是布局相关的一些信息,还有 Field 对于的 ReactElement.- 封装了
onFinishFailed
方法,根据配置scrollToFirstError
会让第一个错误进入 viewport. - 最后套上
SizeContextProvider,FormContext.Provider
里面放上rc-Form
.
看看 FieldItem
FieldItem 是在rc-Field
基础上添加了一些额外的功能。它主要添加了label,require 标记,tooltip,对于 input,它还添加了errorlist,gethelp 等,还有就是label 与 input的布局,通过colspan,offset来控制一共 24 个格子。这个里面有两个大的分支,布局以及rc-field
的功能。布局要不要由noStyle控制,rc-field
的功能阉割由!hasName && !isRenderProps && !dependencies
控制
if (!hasName && !isRenderProps && !dependencies) {
return renderLayout(children) as JSX.Element;
}
- 首先获取从
ConfigContext
中获取getPrefixCls
,从antd.FormContext
中获取formName,requireMark
,创建FormItemContext
这里面就一个方法updateItemErrors: (name: string, errors: string[], originName?: string) => void
- 最终每个
FieldItemField
被封装成,如下的结构。FormItemLabel
是label
,required由requiredMark决定,colon也由它接管,由colon控制。同时还可以支持tooltip。这里面有两个分支,一个是Field
,另一个是普通的控件,跟 form 无关,另一个是Field
。判断条件是 FieldItem
里面还有一个noStyle
, 这个值是控制是否要显示label,error,设置这个值表示,这些东西由父级接管了。
if (!hasName && !isRenderProps && !dependencies) {
return renderLayout(children) as JSX.Element;
}
<Row>
<FormItemLabel />
<FormItemInput>{children}</FormItemInput>
</Row>
FieldItemInput
它其实包含inputDom, errorListDom, extraDom
三部分,inputDom 如下,errorList 也如下。ErrorList
有点怪,功能很简单,就是把 error 挨个显示出来,但是用了一个useCacheErrors
,这个方法很简单,但是触发的过程确实好复杂。有好几层异步,还不明白为什么搞这么复杂。这段代码应该是打补丁打出来的。
const inputDom = (
<div className={`${baseClassName}-control-input`}>
<div className={`${baseClassName}-control-input-content`}>{children}</div>
{icon}
</div>
);
const errorListDom = (
<FormItemPrefixContext.Provider value={{ prefixCls, status }}>
<ErrorList
errors={errors}
help={help}
onDomErrorVisibleChange={onDomErrorVisibleChange}
/>
</FormItemPrefixContext.Provider>
);
再聊聊几个参数组合使用的禁忌
shouldUpdate
与dependencies
不可以同时使用。- 如果
Item
里面的 children 是数组,则不能使用 name。 - 如果 children 是 renderProps,则必须跟 shouldUpdate 或者 dependencies 组合使用。同时 Field 不能使用 name,因为 renderProps 其实是一个可变的逻辑,它里面的内容才是 Field。
- 如果设置了 dependencies, 要么指定名字,要么使用 renderProps。
聊聊 dependencies vs shouldUpdate,
- 共同点,这两个都是依据一定的条件决定当前的 Field 要不要刷新。
- 不同点,dependencies,一般用于 validation, 也就是 upstream 的 Field 更新了,当前的 Field 要不要刷新,这个主要是用来激发验证的逻辑。如果当前的 Field 里面需要根据 upstream 里面的值来决定显示哪些 Field,那么这种情况下推荐用 shouldUpdate。因为,如果我们调用
FormInstance.setFieldsValue()
时,这些隐藏的 Field 不会被刷新。只要用 shouldUpdate 才会。参考下面的例子。
BugHere
<Item name='gender'
label='Gender'
rules={[{required:true}]}
>
<Select
placeholder="Select a option and change input text above"
onChange={onGenderChange}
allowClear
>
<Option value='male'>Male</Option>
<Option value='female'>Female</Option>
<Option value='other'>Other</Option>
</Select>
</Item>
<Item dependencies={['gender']}
//shouldUpdate={(prev,cur)=>prev.gender!=cur.gender}
noStyle
>
{({getFieldValue})=>{
const gender = getFieldValue(['gender']);
return gender === 'other'?(
<Item name="customizeGender"
label="Customize Gender"
rules={[{required:true}]}
>
<Input/>
</Item>
):null;
}}
</Item>