rc-form 源码解读

用户头像
Lee Chen
关注
发布于: 2020 年 07 月 09 日
rc-form源码解读

阅读更多系列文章请访问我的GitHub博客,本文示例代码请访问

这里



前言



在开发过程中,进行表单校验是一个很常用的功能。



表单校验通常需要实现以下几个功能:



  1. 收集各表单项的数据,如Input输入框,Select选择框等。



  1. 按照需求,对表单项数据进行校验,并显示校验结果。



  1. 需要提交表单时,对表单中所有数据进行校验,并收集所有数据。



这些功能看似简单,自己实现的话,还是会产生不少问题。因此,最好使用已有的库来实现此功能。我在开发中通常使用Ant DesignForm组件。从文档中的介绍可以看出,Form组件的功能实现主要是引用了rc-form



rc-form在开发中帮了我不少忙,我对它的实现方式也很感兴趣,于是研究了它的源码,现在与大家分享一下。



阅读本文你将得到什么



我将为你梳理rc-form的主要实现思路,为你讲解rc-form源码中部分方法的用途,并提供部分代码的注释。



最后,我还会自己实现一个精简版的rc-form组件,供你参考。



开始前准备



  1. 本文中的Demo使用TypeScript编写,如果你对TypeScript不了解,可以查看TypeScript文档。但只要有JavaScript基础,就不会影响阅读。



  1. 为了方便理解,你还可以从GitHub下载Ant Designrc-form的源码。



  1. 获取文中示例项目代码,请点击这里



Demo运行方法:



$ yarn install
$ yarn start # visit http://localhost:3000/



运行后你会看到一个这样的Demo页面:





rc-form表单Demo



下图是一个rc-form表单Demo,你可以在http://localhost:3000/页面中,点击表单弹窗按钮,查看效果。





这个表单实现了如下功能:



  1. 在用户输入时和点击确认按钮时,会进行表单项的非空校验。



  1. 用户输入和选择的结果,会显示在表单下方。



  1. 点击确认按钮,若校验通过,会弹窗提示用户输入结果。



rc-form表单Demo实现代码



该表单是使用Ant Design Form组件实现的,代码如下:



示例代码位置:/src/components/FormModal.tsx



import React from 'react'
import {Form, Input, Select} from 'antd'
import {FormComponentProps} from 'antd/lib/form'
import FormItem, {FormItemProps} from 'antd/lib/form/FormItem'
import Modal, {ModalProps} from 'antd/lib/modal'

const Option = Select.Option

// FormItem宽度兼容
export const formItemLayout: FormItemProps = {
labelCol: {
xs: {span: 24},
sm: {span: 6}
},
wrapperCol: {
xs: {span: 24},
sm: {span: 16}
}
}
// 性别枚举
enum SexEnum {
male = 'male',
female = 'female'
}

// 性别名称枚举
enum SexNameEnum {
male = '男',
female = '女'
}

// 表单字段类型
export class FormModalValues {
username: string = ''
sex: SexEnum = SexEnum.male
}

export interface Props extends ModalProps, FormComponentProps {

}

export class State {
visible: boolean = false
}

export class FormModalComponent extends React.Component<Props, State> {

constructor(props: Props) {
super(props)

this.state = new State()
}

// 打开弹窗
public show = (): void => {
this.setState({
visible: true
})
}

// 关闭弹窗
public hide = (): void => {
this.setState({
visible: false
})
}

public onOk = async () => {
// 方法1:使用回调函数获取表单验证结果
/* this.props.form.validateFields(async (errors: any, {username, sex}: FormModalValues) => {
if (!errors) {
Modal.success({
title: '表单输入结果',
content: `用户名:${username},性别:${SexNameEnum[sex]}。`
})
this.hide()
}
}) */
// 方法2:使用async函数获取表单验证结果
try {
// @ts-ignore
const {username, sex}: FormModalValues = await this.props.form.validateFields()
Modal.success({
title: '表单输入结果',
content: `用户名:${username},性别:${SexNameEnum[sex]}。`
})
this.hide()
} catch (error) {
console.error(error)
return error
}
}

// 关闭弹窗后初始化弹窗参数
public afterClose = (): void => {
// 重置表单
this.props.form.resetFields()
this.setState(new State())
}

componentDidMount() {
// 为表单设置初始值,这里与getFieldDecorator方法中的initialValue重复。
this.props.form.setFieldsValue(new FormModalValues())
}

render() {
const form = this.props.form
// 获取用户输入的表单数据
const username: string = form.getFieldValue('username')
const sex: SexEnum = form.getFieldValue('sex')

return (
<Modal
visible={this.state.visible}
title={'新建用户'}
maskClosable
onCancel={this.hide}
onOk={this.onOk}
afterClose={this.afterClose}
>
<FormItem
label={'请输入用户名'}
required={true}
{...formItemLayout}
>
{
// getFieldDecorator为表单字段绑定value和onChange等事件,并实现校验等功能
form.getFieldDecorator<FormModalValues>(
// 表单项数据字段
'username',
{
// 表单初始值
initialValue: '',
// 表单校验规则
rules: [
{
required: true,
}
]
}
)(
<Input />
)
}
</FormItem>
<FormItem
label={'请选择性别'}
required={true}
{...formItemLayout}
>
{
// getFieldDecorator为表单字段绑定value和onChange等事件,并实现校验等功能
form.getFieldDecorator<FormModalValues>(
// 表单项数据字段
'sex',
{
// 表单初始值
initialValue: SexEnum.male,
// 表单校验规则
rules: [
{
required: true,
}
]
}
)(
<Select
style={{width: '60px'}}
>
<Option
value={'male'}
>
</Option>
<Option
value={'female'}
>
</Option>
</Select>
)
}
</FormItem>
<FormItem
label={'输入的用户名'}
{...formItemLayout}
>
{username}
</FormItem>
<FormItem
label={'选择的性别'}
{...formItemLayout}
>
{
SexNameEnum[sex]
}
</FormItem>
</Modal>
)
}

}

const FormModal = Form.create<Props>()(FormModalComponent)

export default FormModal



实现Demo用到的方法



在这个Demo中,我们主要用到了以下几个方法:



  1. Form.create:创建一个新的表单组件,提供表单校验、获取数据等方法,以及存储表单数据功能。



  1. this.props.form.getFieldDecorator:为表单字段绑定value和onChange等事件,并实现校验等功能。



  1. this.props.form.getFieldValue:获取表单字段值。



  1. this.props.form.setFieldsValue:为表单字段设置值。



  1. this.props.form.validateFields:进行表单校验,并返回校验结果和当前表单数据。



  1. this.props.form.resetFields:重置表单数据为初始值。



Form.create方法解读



上面列出的方法中,除了Form.create,都是rc-form提供的方法。



但如果查看create方法的实现方式,可以发现它直接调用了rc-form下的createDOMForm,如下:



示例代码位置:/ant-design/components/form/Form.tsx



import createDOMForm from 'rc-form/lib/createDOMForm';

static create = function create<TOwnProps extends FormComponentProps>(
options: FormCreateOption<TOwnProps> = {},
): FormWrappedProps<TOwnProps> {
return createDOMForm({
fieldNameProp: 'id',
...options,
fieldMetaProp: FIELD_META_PROP,
fieldDataProp: FIELD_DATA_PROP,
});
};



createDOMForm方法解读



查看rc-form源码,可以看到createDOMForm方法仅仅是调用了createBaseForm方法。



createDOMForm示例代码位置:/rc-form/src/createDOMForm.js



function createDOMForm(option) {
return createBaseForm({
...option,
}, [mixin]);
}



高阶组件(HOC)



createBaseForm的实现方式



现在我们的重点应当放在createBaseForm方法上,不过它的代码足足有600多行,很难在短时间内弄清楚所有细节。



但我们只要理解createBaseForm的大体结构,就可以知道它主要完成了哪些功能。



以下是我简化过的createBaseForm代码:



createBaseForm示例代码位置:/rc-form/src/createBaseForm.js



function createBaseForm(option = {}, mixins = []) {
return function decorate(WrappedComponent) {
const Form = createReactClass({
render() {
return <WrappedComponent {...props} />
}
})

return Form
}
}

export default createBaseForm



从这段代码可以看出,createBaseForm方法实际上就是实现了一个高阶组件(HOC)。



getFieldDecorator的实现方式



我们现在已经知道createBaseForm其实是一个高阶组件(HOC),那么再来看与之用法相似的getFieldDecorator方法,它的也是实现了一个高阶组件(HOC)`。



我简化过的getFieldDecorator代码如下:



getFieldDecorator示例代码位置:/rc-form/src/createBaseForm.js



getFieldDecorator(name, fieldOption) {
const props = this.getFieldProps(name, fieldOption);

return (fieldElem) => {
return React.cloneElement(fieldElem, {
...props,
});
};
}



高阶组件(HOC)简介



高阶组件是一个获取组件并返回新组件的函数。



高阶组件(HOC)有以下特点:



  1. 高阶组件是对已有组件的封装,形成了一个新组件,新组件实现了特定的业务逻辑,并将其通过props传给原有组件。



  1. 高阶组件通常不需要实现UI,其UI由传入的原组件实现,它只是为原组件提供了额外的功能或数据。



高阶组件(HOC)Demo



下面来看一个简单的HOC例子:



示例代码位置:/src/utils/createTimer.tsx



import React from 'react'

export interface Props {
wrappedComponentRef?: React.RefObject<any>
}

export class State {
time: Date = new Date()
}

export interface TimerProps {
time: Date
}

function createTimer(WrappedComponent: React.ComponentClass<TimerProps>): React.ComponentClass<Props> {

class Timer extends React.Component<Props, State> {

timer: number = 0

constructor(props: Props) {
super(props)

this.state = new State()
}

componentDidMount() {
this.timer = window.setInterval(() => {
this.setState({
time: new Date()
})
}, 1000)
}

componentWillUnmount() {
clearInterval(this.timer)
}

render() {
// 为原组件提供time的props后,将其作为组件返回显示,不对UI做修改
return (
<WrappedComponent
ref={this.props.wrappedComponentRef}
time={this.state.time}
/>
)
}

}

// 返回新组件
return Timer

}

export default createTimer



这个例子实现了一个计时器的高阶组件,它将当前要显示的时间通过props中名为time的属性传入原组件。



同时,在返回的新组件中,可以通过设置wrappedComponentRef属性,可以获取到原组件。



高阶组件(HOC)的简单使用



下面是一个使用createTimer显示计时器的一个简单例子,该组件接收了HOC传过来的time属性,放入一个p标签中显示。



你可以在http://localhost:3000/页面中,表单弹窗按钮下方看到显示的时间。





示例代码位置:/src/components/ShowTimer.tsx



import React from 'react'
import moment from 'moment'
import createTimer, {TimerProps} from '../utils/createTimer';

// 表单字段类型
export interface Props extends TimerProps {

}

export class State {

}

export class ShowTimerComponent extends React.Component<Props, State> {

constructor(props: Props) {
super(props)

this.state = new State()
}

render() {
return (
<p>
{moment(this.props.time).format('YYYY-MM-DD HH:mm:ss')}
</p>
)
}

}

// 导出用HOC创建的新组件
const ShowTimer = createTimer(ShowTimerComponent)

export default ShowTimer



高阶组件(HOC)的结合弹窗使用



下面是一个使用createTimer创建弹窗显示计时器的例子,弹窗组件接收了HOC传过来的time属性,并将其显示出来。



同时将弹窗组件通过wrappedComponentRef属性提供给外部使用,实现了打开、关闭弹窗功能。



你可以在http://localhost:3000/页面中,点击时间弹窗按钮,查看效果。





示例代码位置:/src/components/ShowTimerModal.tsx



import React from 'react'
import moment from 'moment'
import {Modal} from 'antd';
import {ModalProps} from 'antd/lib/modal';
import createTimer, {TimerProps} from '../utils/createTimer';

// 表单字段类型
export interface Props extends ModalProps, TimerProps {

}

export class State {
visible: boolean = false
}

export class ShowTimerModalComponent extends React.Component<Props, State> {

constructor(props: Props) {
super(props)

this.state = new State()
}

// 打开弹窗
public show = (): void => {
this.setState({
visible: true
})
}

// 关闭弹窗
public hide = (): void => {
this.setState({
visible: false
})
}

render() {
return (
<Modal
visible={this.state.visible}
title={'弹窗显示时间'}
maskClosable
cancelButtonProps={{style: {display: 'none'}}}
onCancel={this.hide}
onOk={this.hide}
>
{moment(this.props.time).format('YYYY-MM-DD HH:mm:ss')}
</Modal>
)
}

}

// 导出用HOC创建的新组件
const ShowTimerModal = createTimer(ShowTimerModalComponent)

export default ShowTimerModal



rc-form源码解读



阅读源码的意见



有了HOC的知识作为铺垫,我们就可以正式进入rc-form源码解读了。



开始正式的解读之前,我先说说我个人对于阅读源码的意见,以rc-form为例,它实现的功能虽然不是十分复杂,但由于这是一个要提供给很多人使用的库,为了避免出错,就需要进行很多的校验,以及开发环境提示等等。虽然这些都是必要的,但却会导致代码十分冗长,如果对代码不熟悉的话,会有不小的阅读障碍。



因此,我个人认为在阅读源码的时候,可以把重点放在以下两个方面:



  1. 理解作者进行开发的思路。就如同谈Redux的时候,都要了解的Flux架构。建议在阅读源码的时候,重点放在理解作者“为什么要这么做”,而不是研究作者是如何实现某个功能的。



  1. 学习作者的优秀习惯、技巧。上面说重点要理解作者的思路,但并不是让你放弃关注细节,而是要有取舍地看。一些自己完全有能力实现,或者作者只是在做一些报错提示之类的代码,可以直接跳过。当然如果看到作者的一些优秀习惯、技巧,或者是一些自己没有想过的实现方式,还是很有必要借鉴的。



rc-form的实现思路



我梳理了rc-form的实现思路,供大家参考,本次源码解读会按照下图进行讲解。建议你在查看rc-form源码时,时常对照这张图,这样更加便于理解。





在之前的高阶组件(HOC)讲解中,已经解读过createBaseForm方法的实现方式,这里就不再赘述。



接下来将依次以createBaseForm中的各个方法,讲解一下rc-form的实现逻辑,每段代码解读都会提供该段代码实现的主要功能,为方便理解,在代码中也提供了部分注释。



getInitialState方法解读



createBaseForm使用了createReactClass方法创建一个React组件类,getInitialState主要用来为组件创建一些初始化参数、方法等,相当于ES6中的constructor



从代码中可以看到,createFieldsStore方法为该组件创建了存储、读取、设置表单数据等功能,并存储在this.fieldsStore属性中。



表单原始数据,如initialValue(表单项初始值)、rules(表单校验规则)等,都会存储在this.fieldsStore.fieldsMeta属性中。



当前的表单数据,会存储在this.fieldsStore.fields属性中。



示例代码位置:/rc-form/src/createBaseForm.js



getInitialState() {
// option.mapPropsToFields: 将值从props转换为字段。用于从redux store读取字段。
const fields = mapPropsToFields && mapPropsToFields(this.props);
// createFieldsStore为该组件提供了存储、读取、设置表单数据等功能
this.fieldsStore = createFieldsStore(fields || {});

this.instances = {};
this.cachedBind = {};
this.clearedFieldMetaCache = {};

this.renderFields = {};
this.domFields = {};

// 为组件绑定了一系列方法,这些方法会通过props传入新组件供其使用
// HACK: https://github.com/ant-design/ant-design/issues/6406
['getFieldsValue',
'getFieldValue',
'setFieldsInitialValue',
'getFieldsError',
'getFieldError',
'isFieldValidating',
'isFieldsValidating',
'isFieldsTouched',
'isFieldTouched'].forEach(key => {
this[key] = (...args) => {
if (process.env.NODE_ENV !== 'production') {
warning(
false,
'you should not use `ref` on enhanced form, please use `wrappedComponentRef`. ' +
'See: https://github.com/react-component/form#note-use-wrappedcomponentref-instead-of-withref-after-rc-form140'
);
}

// 该组件中的方法,直接调用了fieldsStore中的方法,也就是由createFieldsStore方法创建的
return this.fieldsStore[key](...args);
};
});

return {
submitting: false,
};
}



render解读



render方法实现了组装新组件需要的props属性与方法,并将其传入新组件,这是一个普通的高阶组件实现方式。



新组件的props主要来自于createBaseForm.jscreateForm.js中定义的mixin对象,如下面的代码:



示例代码位置:/rc-form/src/createForm.js



export const mixin = {
getForm() {
return {
getFieldsValue: this.fieldsStore.getFieldsValue,
getFieldValue: this.fieldsStore.getFieldValue,
getFieldInstance: this.getFieldInstance,
setFieldsValue: this.setFieldsValue,
setFields: this.setFields,
setFieldsInitialValue: this.fieldsStore.setFieldsInitialValue,
getFieldDecorator: this.getFieldDecorator,
getFieldProps: this.getFieldProps,
getFieldsError: this.fieldsStore.getFieldsError,
getFieldError: this.fieldsStore.getFieldError,
isFieldValidating: this.fieldsStore.isFieldValidating,
isFieldsValidating: this.fieldsStore.isFieldsValidating,
isFieldsTouched: this.fieldsStore.isFieldsTouched,
isFieldTouched: this.fieldsStore.isFieldTouched,
isSubmitting: this.isSubmitting,
submit: this.submit,
validateFields: this.validateFields,
resetFields: this.resetFields,
};
},
};



也就是说,mixin定义了需要传递给新组件使用的方法。



示例代码位置:/rc-form/src/createBaseForm.js



render() {
const {wrappedComponentRef, ...restProps} = this.props; // eslint-disable-line
const formProps = {
// getForm方法来自于createDOMForm方法调用createBaseForm方法时,传入的mixin对象
// mixin合并了createForm.js中导出的的mixin
[formPropName]: this.getForm(),
};
if (withRef) {
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
warning(
false,
'`withRef` is deprecated, please use `wrappedComponentRef` instead. ' +
'See: https://github.com/react-component/form#note-use-wrappedcomponentref-instead-of-withref-after-rc-form140'
);
}
formProps.ref = 'wrappedComponent';
} else if (wrappedComponentRef) {
formProps.ref = wrappedComponentRef;
}

// 创建新组件的props
const props = mapProps.call(this, {
...formProps,
...restProps,
});
return <WrappedComponent {...props} />;
},
});



getFieldDecorator(HOC)解读



getFieldDecorator方法主要实现了一个高阶组件(HOC),它主要为新组件增加了绑定value属性和onChange事件,以及实现了onChange时的表单校验功能。



新组件的props是通过getFieldProps方法创建,该方法主要实现了绑定onChange事件,确保表单能够获取到表单项输入的值,在onChange的同时使用async-validator进行校验。



示例代码位置:/rc-form/src/createBaseForm.js



// 获取当前表单项的field、fieldMeta数据
onCollectCommon(name, action, args) {
const fieldMeta = this.fieldsStore.getFieldMeta(name);
if (fieldMeta[action]) {
fieldMeta[action](...args);
} else if (fieldMeta.originalProps && fieldMeta.originalProps[action]) {
fieldMeta.originalProps[action](...args);
}
// 通过getValueFromEvent方法,从event中获取当前表单项的值,fieldMeta.getValueFromEvent为用户自定义的方法。
const value = fieldMeta.getValueFromEvent ?
fieldMeta.getValueFromEvent(...args) :
getValueFromEvent(...args);
if (onValuesChange && value !== this.fieldsStore.getFieldValue(name)) {
const valuesAll = this.fieldsStore.getAllValues();
const valuesAllSet = {};
valuesAll[name] = value;
Object.keys(valuesAll).forEach(key => set(valuesAllSet, key, valuesAll[key]));
onValuesChange({
[formPropName]: this.getForm(),
...this.props
}, set({}, name, value), valuesAllSet);
}
// 获取相应字段的field数据
const field = this.fieldsStore.getField(name);
return ({name, field: {...field, value, touched: true}, fieldMeta});
},

// 设置表单数据
onCollect(name_, action, ...args) {
// 获取当前表单数据及设置
const {name, field, fieldMeta} = this.onCollectCommon(name_, action, args);
const {validate} = fieldMeta;

this.fieldsStore.setFieldsAsDirty();

const newField = {
...field,
dirty: hasRules(validate),
};
this.setFields({
[name]: newField,
});
},

onCollectValidate(name_, action, ...args) {
// 获取当前表单数据及设置
const {field, fieldMeta} = this.onCollectCommon(name_, action, args);
const newField = {
...field,
dirty: true,
};

this.fieldsStore.setFieldsAsDirty();

// 进行表单校验,并存储表单数据
this.validateFieldsInternal([newField], {
action,
options: {
firstFields: !!fieldMeta.validateFirst,
},
});
},

// 返回一个表单项的onChange事件
getCacheBind(name, action, fn) {
if (!this.cachedBind[name]) {
this.cachedBind[name] = {};
}
const cache = this.cachedBind[name];
if (!cache[action] || cache[action].oriFn !== fn) {
cache[action] = {
fn: fn.bind(this, name, action),
oriFn: fn,
};
}
return cache[action].fn;
},

// 创建新的表单项组件
getFieldDecorator(name, fieldOption) {
// 注册表单项,获取新表单项的props,主要是value属性和onChange事件等
const props = this.getFieldProps(name, fieldOption);

return (fieldElem) => {
// We should put field in record if it is rendered
this.renderFields[name] = true;

const fieldMeta = this.fieldsStore.getFieldMeta(name);
const originalProps = fieldElem.props;

// 这段是在生产环境的打印提示语
if (process.env.NODE_ENV !== 'production') {
const valuePropName = fieldMeta.valuePropName;
warning(
!(valuePropName in originalProps),
`\`getFieldDecorator\` will override \`${valuePropName}\`, ` +
`so please don't set \`${valuePropName}\` directly ` +
`and use \`setFieldsValue\` to set it.`
);
const defaultValuePropName =
`default${valuePropName[0].toUpperCase()}${valuePropName.slice(1)}`;
warning(
!(defaultValuePropName in originalProps),
`\`${defaultValuePropName}\` is invalid ` +
`for \`getFieldDecorator\` will set \`${valuePropName}\`,` +
` please use \`option.initialValue\` instead.`
);
}

fieldMeta.originalProps = originalProps;
fieldMeta.ref = fieldElem.ref;

return React.cloneElement(fieldElem, {
...props,
// 该方法用于返回当前表单存储的value值
...this.fieldsStore.getFieldValuePropValue(fieldMeta),
});
};
},

// 创建表单项组件的props
getFieldProps(name, usersFieldOption = {}) {
if (!name) {
throw new Error('Must call `getFieldProps` with valid name string!');
}
if (process.env.NODE_ENV !== 'production') {
warning(
this.fieldsStore.isValidNestedFieldName(name),
`One field name cannot be part of another, e.g. \`a\` and \`a.b\`. Check field: ${name}`
);
warning(
!('exclusive' in usersFieldOption),
'`option.exclusive` of `getFieldProps`|`getFieldDecorator` had been remove.'
);
}

delete this.clearedFieldMetaCache[name];

const fieldOption = {
name,
trigger: DEFAULT_TRIGGER,
valuePropName: 'value',
validate: [],
...usersFieldOption,
};

const {
rules,
trigger,
validateTrigger = trigger,
validate,
} = fieldOption;

const fieldMeta = this.fieldsStore.getFieldMeta(name);
if ('initialValue' in fieldOption) {
fieldMeta.initialValue = fieldOption.initialValue;
}

const inputProps = {
...this.fieldsStore.getFieldValuePropValue(fieldOption),
ref: this.getCacheBind(name, `${name}__ref`, this.saveRef),
};
if (fieldNameProp) {
inputProps[fieldNameProp] = formName ? `${formName}_${name}` : name;
}

// 获取表单项校验触发事件及校验规则
const validateRules = normalizeValidateRules(validate, rules, validateTrigger);
// 获取表单项校验事件
const validateTriggers = getValidateTriggers(validateRules);
validateTriggers.forEach((action) => {
// 若已绑定了校验事件,则返回
if (inputProps[action]) return;
// 绑定收集表单数据及校验事件
inputProps[action] = this.getCacheBind(name, action, this.onCollectValidate);
});

// 若validateTriggers为空,则绑定普通事件,不进行校验
// 使用onCollect方法,获取绑定表单项输入值事件,将其存储到inputProps中,并返回给组件用作props
// make sure that the value will be collect
if (trigger && validateTriggers.indexOf(trigger) === -1) {
// getCacheBind负责返回一个表单项的onChange事件
inputProps[trigger] = this.getCacheBind(name, trigger, this.onCollect);
}

// 将当前已设置的表单选项,与新表单选项合并,并存入fieldsMeta属性
const meta = {
...fieldMeta,
...fieldOption,
validate: validateRules,
};
// 注册表单项,将表单设置如initialValue、validateRules等,存储到this.fieldsStore.fieldsMeta[name]中
this.fieldsStore.setFieldMeta(name, meta);
if (fieldMetaProp) {
inputProps[fieldMetaProp] = meta;
}

if (fieldDataProp) {
inputProps[fieldDataProp] = this.fieldsStore.getField(name);
}

// This field is rendered, record it
this.renderFields[name] = true;

return inputProps;
},



示例代码位置:/rc-form/src/utils.js



/**
* 将validate、rules、validateTrigger三个参数配置的校验事件及规则,整理成统一的校验事件、规则
* @param {Array<object>} validate 校验事件、规则
* @param {string} validate[].trigger 校验事件
* @param {object[]} validate[].rules 校验规则,参考async-validator,https://github.com/yiminghe/async-validator
* @param {object[]} rules 校验规则,参考async-validator,https://github.com/yiminghe/async-validator
* @param {string} validateTrigger 校验事件
* @returns {Array<object>} validateRules 校验事件、规则
* @returns {string[]} validateRules[].trigger 校验事件
* @returns {object[]} validateRules[].rules 校验规则,参考async-validator,https://github.com/yiminghe/async-validator
*/
export function normalizeValidateRules(validate, rules, validateTrigger) {
const validateRules = validate.map((item) => {
const newItem = {
...item,
trigger: item.trigger || [],
};
if (typeof newItem.trigger === 'string') {
newItem.trigger = [newItem.trigger];
}
return newItem;
});
if (rules) {
validateRules.push({
trigger: validateTrigger ? [].concat(validateTrigger) : [],
rules,
});
}
return validateRules;
}

/**
* 将validate、rules、validateTrigger三个参数配置的校验事件及规则,整理成统一的校验事件、规则
* @param {Array<object>} validateRules 校验事件、规则
* @param {string[]} validateRules[].trigger 校验事件
* @param {object[]} validateRules[].rules 校验规则,参考async-validator,https://github.com/yiminghe/async-validator
* @returns {Array<string>} 校验事件
*/
export function getValidateTriggers(validateRules) {
return validateRules
.filter(item => !!item.rules && item.rules.length)
.map(item => item.trigger)
.reduce((pre, curr) => pre.concat(curr), []);
}

// 判断表单项类型,获取表单数据,默认支持通过event.target.value或event.target.checked获取
export function getValueFromEvent(e) {
// To support custom element
if (!e || !e.target) {
return e;
}
const {target} = e;
return target.type === 'checkbox' ? target.checked : target.value;
}



getFieldsValue/getFieldValue解读



createBaseForm.js中并未实现getFieldsValuegetFieldValue方法,而是直接调用了this.fieldsStore.getFieldsValuethis.fieldsStore.getFieldValue方法,它们实现的功能是从存储的数据中,查找出指定的数据。



this.fieldsStore.getFieldsValue方法如未指定需要查找的数据,则返回所有数据。



this.fieldsStore.getNestedField是一个公用方法,根据传入的字段名,或者表单已存储的字段名,使用传入的回调函数获取所需的数据。



this.fieldsStore.getValueFromFields方法,根据传入的字段名,获取当前表单的值,若值不存在,则返回已设置的initialValue。



示例代码位置:/rc-form/src/createFieldsStore.js



getFieldsValue = (names) => {
return this.getNestedFields(names, this.getFieldValue);
}

getNestedFields(names, getter) {
const fields = names || this.getValidFieldsName();
return fields.reduce((acc, f) => set(acc, f, getter(f)), {});
}

getFieldValue = (name) => {
const {fields} = this;
return this.getNestedField(name, (fullName) => this.getValueFromFields(fullName, fields));
}

// 从传入的fields中,按name获取相应的值,若没有则直接返回fieldMeta中设置的initialValue
getValueFromFields(name, fields) {
const field = fields[name];
if (field && 'value' in field) {
return field.value;
}
const fieldMeta = this.getFieldMeta(name);
return fieldMeta && fieldMeta.initialValue;
}

// 根据传入的name,获取fieldMeta中存在的字段名称,最终调用getter函数获取相应的值
getNestedField(name, getter) {
const fullNames = this.getValidFieldsFullName(name);
if (
fullNames.length === 0 || // Not registered
(fullNames.length === 1 && fullNames[0] === name) // Name already is full name.
) {
return getter(name);
}
const isArrayValue = fullNames[0][name.length] === '[';
const suffixNameStartIndex = isArrayValue ? name.length : name.length + 1;
return fullNames
.reduce(
(acc, fullName) => set(
acc,
fullName.slice(suffixNameStartIndex),
getter(fullName)
),
isArrayValue ? [] : {}
);
}

// 获取存储的表单字段名称
getValidFieldsFullName(maybePartialName) {
const maybePartialNames = Array.isArray(maybePartialName) ?
maybePartialName : [maybePartialName];
return this.getValidFieldsName()
.filter(fullName => maybePartialNames.some(partialName => (
fullName === partialName || (
startsWith(fullName, partialName) &&
['.', '['].indexOf(fullName[partialName.length]) >= 0
)
)));
}

// 获取存储的表单字段名称
getValidFieldsName() {
const {fieldsMeta} = this;
// 过滤出fieldsMeta中存储的未被设置为hidden的数据
return fieldsMeta ?
Object.keys(fieldsMeta).filter(name => !this.getFieldMeta(name).hidden) :
[];
}

// 获取存储的字段数据
getFieldMeta(name) {
this.fieldsMeta[name] = this.fieldsMeta[name] || {};
return this.fieldsMeta[name];
}



setFieldsValue解读



setFieldsValue方法,实现的就是设置表单数据的功能,代码按如下流程调用:



this.setFieldsValuethis.setFieldsthis.fieldsStore.setFields



最终新数据存储在this.fieldsStore.fields中。



示例代码位置:/rc-form/src/createBaseForm.js



setFieldsValue(changedValues, callback) {
const {fieldsMeta} = this.fieldsStore;

// 过滤出已注册的表单项的值
const values = this.fieldsStore.flattenRegisteredFields(changedValues);
const newFields = Object.keys(values).reduce((acc, name) => {
const isRegistered = fieldsMeta[name];
if (process.env.NODE_ENV !== 'production') {
warning(
isRegistered,
'Cannot use `setFieldsValue` until ' +
'you use `getFieldDecorator` or `getFieldProps` to register it.'
);
}
if (isRegistered) {
const value = values[name];
acc[name] = {
value,
};
}
return acc;
}, {});

// 设置表单的值
this.setFields(newFields, callback);

if (onValuesChange) {
const allValues = this.fieldsStore.getAllValues();
onValuesChange({
[formPropName]: this.getForm(),
...this.props
}, changedValues, allValues);
}
}

setFields(maybeNestedFields, callback) {
const fields = this.fieldsStore.flattenRegisteredFields(maybeNestedFields);
this.fieldsStore.setFields(fields);
if (onFieldsChange) {
const changedFields = Object.keys(fields)
.reduce((acc, name) => set(acc, name, this.fieldsStore.getField(name)), {});
onFieldsChange({
[formPropName]: this.getForm(),
...this.props
}, changedFields, this.fieldsStore.getNestedAllFields());
}
this.forceUpdate(callback);
}



示例代码位置:/rc-form/src/createFieldsStore.js



// 设置表单值
setFields(fields) {
const fieldsMeta = this.fieldsMeta;
// 将当前数据和传入的新数据合并
const nowFields = {
...this.fields,
...fields,
};
const nowValues = {};
// 按照fieldsMeta中已注册的字段,从nowFields中取出这些字段的最新值,如果为空则设置为initialValue,形成新表单数据nowValues
Object.keys(fieldsMeta)
.forEach((f) => {
nowValues[f] = this.getValueFromFields(f, nowFields);
});
// 如果该表单项有设置normalize方法,则返回normalize之后的数据
// 可参考如这个例子:https://codepen.io/afc163/pen/JJVXzG?editors=001
Object.keys(nowValues).forEach((f) => {
const value = nowValues[f];
const fieldMeta = this.getFieldMeta(f);
if (fieldMeta && fieldMeta.normalize) {
const nowValue =
fieldMeta.normalize(value, this.getValueFromFields(f, this.fields), nowValues);
if (nowValue !== value) {
nowFields[f] = {
...nowFields[f],
value: nowValue,
};
}
}
});
this.fields = nowFields;
}



resetFields解读



resetFields方法,实现了重置表单为初始值功能。它的实现方式是:



  1. 先调用this.fieldsStore.resetFields方法,清空所有表单数据。



  1. 调用this.fieldsStore.setFields方法设置表单数据。因设置数据时并未传入新数据,默认会设置为fieldsMeta中存储的initialValue,以达到重置表单的目的。



示例代码位置:/rc-form/src/createBaseForm.js



resetFields(ns) {
// 获取清空了所有fields中存储数据的对象
const newFields = this.fieldsStore.resetFields(ns);
if (Object.keys(newFields).length > 0) {
// 为newFields中的各个字段赋值,由于数据都为空,则会从fieldsMeta中查找initialValue赋值。
this.setFields(newFields);
}
if (ns) {
const names = Array.isArray(ns) ? ns : [ns];
names.forEach(name => delete this.clearedFieldMetaCache[name]);
} else {
this.clearedFieldMetaCache = {};
}
}



示例代码位置:/rc-form/src/createFieldsStore.js



resetFields(ns) {
const {fields} = this;
// 获取需要重置的字段名称
const names = ns ?
this.getValidFieldsFullName(ns) :
this.getAllFieldsName();

// 如果当前fields中存在数据,则清空。最终返回的是清空了所有现有数据的对象。
return names.reduce((acc, name) => {
const field = fields[name];
if (field && 'value' in field) {
acc[name] = {};
}
return acc;
}, {});
}



getFieldError解读



this.getFieldError用于获取传入表单项的校验结果,包括校验的属性名称和提示语。



this.getFieldError的调用方式与this.getFieldValue类似,最终也是通过调用this.fieldsStore.getNestedField方法,同时传入相应的回调函数,获取到需要的校验结果。



示例代码位置:/rc-form/src/createBaseForm.js



// 获取校验结果,返回的是校验错误提示语的数组,格式为string[]
getFieldError = (name) => {
return this.getNestedField(
name,
(fullName) => getErrorStrs(this.getFieldMember(fullName, 'errors'))
);
}

// 根据传入的name,获取fieldMeta中存在的字段名称,最终调用getter函数获取相应的值
getNestedField(name, getter) {
const fullNames = this.getValidFieldsFullName(name);
if (
fullNames.length === 0 || // Not registered
(fullNames.length === 1 && fullNames[0] === name) // Name already is full name.
) {
return getter(name);
}
const isArrayValue = fullNames[0][name.length] === '[';
const suffixNameStartIndex = isArrayValue ? name.length : name.length + 1;
return fullNames
.reduce(
(acc, fullName) => set(
acc,
fullName.slice(suffixNameStartIndex),
getter(fullName)
),
isArrayValue ? [] : {}
);
}

// 从当前字段数据中,获取传入member类型的数据
getFieldMember(name, member) {
return this.getField(name)[member];
}

// 获取存储的字段数据及校验结果,并补充字段名称
getField(name) {
return {
...this.fields[name],
name,
};
}



示例代码位置:/rc-form/src/utils.js



// 将错误数据整理后返回,返回的是校验错误提示语的数组,格式为string[]
function getErrorStrs(errors) {
if (errors) {
return errors.map((e) => {
if (e && e.message) {
return e.message;
}
return e;
});
}
return errors;
}



validateFields解读



this.validateFields实现的是,先过滤出有配置rules校验规则的表单项,调用async-validator进行校验,并返回校验结果。



示例代码位置:/rc-form/src/createBaseForm.js



// 校验表单方法
validateFieldsInternal(fields, {
fieldNames,
action,
options = {},
}, callback) {
const allRules = {};
const allValues = {};
const allFields = {};
const alreadyErrors = {};
// 先清空已有的校验结果
fields.forEach((field) => {
const name = field.name;
if (options.force !== true && field.dirty === false) {
if (field.errors) {
set(alreadyErrors, name, {errors: field.errors});
}
return;
}
const fieldMeta = this.fieldsStore.getFieldMeta(name);
const newField = {
...field,
};
newField.errors = undefined;
newField.validating = true;
newField.dirty = true;
allRules[name] = this.getRules(fieldMeta, action);
allValues[name] = newField.value;
allFields[name] = newField;
});
// 设置清空后的表单校验结果
this.setFields(allFields);
// in case normalize
Object.keys(allValues).forEach((f) => {
allValues[f] = this.fieldsStore.getFieldValue(f);
});
if (callback && isEmptyObject(allFields)) {
callback(isEmptyObject(alreadyErrors) ? null : alreadyErrors,
this.fieldsStore.getFieldsValue(fieldNames));
return;
}

// 使用AsyncValidator进行校验,并返回校验结果
const validator = new AsyncValidator(allRules);
if (validateMessages) {
validator.messages(validateMessages);
}
validator.validate(allValues, options, (errors) => {
const errorsGroup = {
...alreadyErrors,
};
// 如果校验不通过,则整理AsyncValidator返回的数据,并存储到表单数据中
if (errors && errors.length) {
errors.forEach((e) => {
const errorFieldName = e.field;
let fieldName = errorFieldName;

// Handle using array validation rule.
// ref: https://github.com/ant-design/ant-design/issues/14275
Object.keys(allRules).some((ruleFieldName) => {
const rules = allRules[ruleFieldName] || [];

// Exist if match rule
if (ruleFieldName === errorFieldName) {
fieldName = ruleFieldName;
return true;
}

// Skip if not match array type
if (rules.every(({type}) => type !== 'array') && errorFieldName.indexOf(ruleFieldName) !== 0) {
return false;
}

// Exist if match the field name
const restPath = errorFieldName.slice(ruleFieldName.length + 1);
if (/^\d+$/.test(restPath)) {
fieldName = ruleFieldName;
return true;
}

return false;
});

const field = get(errorsGroup, fieldName);
if (typeof field !== 'object' || Array.isArray(field)) {
set(errorsGroup, fieldName, {errors: []});
}
const fieldErrors = get(errorsGroup, fieldName.concat('.errors'));
fieldErrors.push(e);
});
}
const expired = [];
const nowAllFields = {};
Object.keys(allRules).forEach((name) => {
const fieldErrors = get(errorsGroup, name);
const nowField = this.fieldsStore.getField(name);
// avoid concurrency problems
if (!eq(nowField.value, allValues[name])) {
expired.push({
name,
});
} else {
nowField.errors = fieldErrors && fieldErrors.errors;
nowField.value = allValues[name];
nowField.validating = false;
nowField.dirty = false;
nowAllFields[name] = nowField;
}
});
// 存储新表单数据及结果
this.setFields(nowAllFields);
if (callback) {
if (expired.length) {
expired.forEach(({name}) => {
const fieldErrors = [{
message: `${name} need to revalidate`,
field: name,
}];
set(errorsGroup, name, {
expired: true,
errors: fieldErrors,
});
});
}

callback(isEmptyObject(errorsGroup) ? null : errorsGroup,
this.fieldsStore.getFieldsValue(fieldNames));
}
});
},

// 校验表单方法,主要用于整理需要校验的表单项数据后,调用validateFieldsInternal进行校验
validateFields(ns, opt, cb) {
const pending = new Promise((resolve, reject) => {
// 因传入的3个参数都为可选,需要将它们整理成固定的names, options, callback参数。
const {names, options} = getParams(ns, opt, cb);
let {callback} = getParams(ns, opt, cb);
if (!callback || typeof callback === 'function') {
const oldCb = callback;
callback = (errors, values) => {
if (oldCb) {
oldCb(errors, values);
} else if (errors) {
reject({errors, values});
} else {
resolve(values);
}
};
}
const fieldNames = names ?
this.fieldsStore.getValidFieldsFullName(names) :
this.fieldsStore.getValidFieldsName();

// 获取需要校验的表单项
const fields = fieldNames
// 过滤出已配置rules的字段
.filter(name => {
const fieldMeta = this.fieldsStore.getFieldMeta(name);
return hasRules(fieldMeta.validate);
})
// 获取当前表单数据
.map((name) => {
const field = this.fieldsStore.getField(name);
field.value = this.fieldsStore.getFieldValue(name);
return field;
});
if (!fields.length) {
callback(null, this.fieldsStore.getFieldsValue(fieldNames));
return;
}
if (!('firstFields' in options)) {
options.firstFields = fieldNames.filter((name) => {
const fieldMeta = this.fieldsStore.getFieldMeta(name);
return !!fieldMeta.validateFirst;
});
}
// 调用表单校验方法,进行校验
this.validateFieldsInternal(fields, {
fieldNames,
options,
}, callback);
});
pending.catch((e) => {
if (console.error && process.env.NODE_ENV !== 'production') {
console.error(e);
}
return e;
});
return pending;
},



实现自己的rc-form



在上一小节,我已经为你梳理了rc-form的实现思路,以及部分常用方法的实现方式。



相信你已经发现,rc-form的实现思路其实不复杂,分别使用HOC为新表单和表单项提供了所需要的扩展方法。



createFieldsStore.js主要是为了实现这些拓展方法,而这些实现较为分复杂,虽然都是必要的,但确实对理解代码造成了一些障碍。



在阅读的时候,可以不必要过于拘泥于其中的细节,其实只要理解了使用HOC进行封装这一点,即使不理解createFieldsStore.js中的具体实现方式,也足够指导我们按照自己的思路来实现rc-form了。



我根据分析的rc-form实现思路,自己实现了一个rc-form功能,你可以在http://localhost:3000/页面中,点击新表单弹窗按钮,查看效果。



实现rc-form示例代码位置:/src/utils/createForm.tsx



import React from 'react'
import {observer} from 'mobx-react';
import {observable, runInAction, toJS} from 'mobx';
import hoistStatics from 'hoist-non-react-statics';
import AsyncValidator, {Rules, ValidateError, ErrorList, RuleItem} from 'async-validator';

// setFieldsValue设置表单数据时传入的数据类型
export class Values {
[propName: string]: any
}

// 表单项设置
export class FieldOption {
initialValue?: any
rules: RuleItem[] = []
}

// 表单项数据
export class Field {
value: any
errors: ErrorList = []
}

// 表格数据
export class Fields {
[propName: string]: Field
}

// 表单项设置数据
export class FieldMeta {
name: string = ''
fieldOption: FieldOption = new FieldOption()
}

// 表格设置数据
export class FieldsMeta {
[propName: string]: FieldMeta
}

export interface Props {
wrappedComponentRef: React.RefObject<React.Component<FormComponentProps, any, any>>
}

// 为原组件添加的form参数
export interface FormProps {
getFieldDecorator: (name: string, fieldOption: FieldOption) => (fieldElem: React.ReactElement) => React.ReactElement
getFieldValue: (name: string) => any
setFieldsValue: (values: any) => void
getFieldsValue: () => any
validateFields: (callback?: (errors: any, values: any) => void) => void
resetFields: () => void
getFieldError: (name: string) => ErrorList
}

// 为原组件添加的props
export interface FormComponentProps {
form: FormProps
}

export class State {

}

function createForm(WrappedComponent: React.ComponentClass<FormComponentProps>): React.ComponentClass<Props> {

@observer
class Form extends React.Component<Props, State> {

// 表单数据
@observable
private fields: Fields = new Fields()

// 表单原始数据
@observable
private fieldsMeta: FieldsMeta = new FieldsMeta()

constructor(props: Props) {
super(props)

this.state = new State()
}

// 创建表单项的props,提供给getFieldDecorator绑定事件
private getFieldProps = (
name: string,
fieldOption: FieldOption = new FieldOption()
): any => {
const initialValue = fieldOption.initialValue

runInAction(() => {
if (!this.fields[name]) {
this.fields[name] = new Field()
if (initialValue) {
this.fields[name].value = initialValue
}
}

if (!this.fieldsMeta[name]) {
this.fieldsMeta[name] = {
name,
fieldOption
}
}
})

return {
value: toJS(this.fields)[name].value,
onChange: (event: React.ChangeEvent<HTMLInputElement> | string): void => {
if (typeof event === 'string') {
this.fields[name].value = event
} else {
this.fields[name].value = event.target.value
}
this.forceUpdate()
this.validateField(name)
}
}
}

// 创建新表单项组件的HOC
private getFieldDecorator = (
name: string,
fieldOption: FieldOption = new FieldOption()
): (fieldElem: React.ReactElement) => React.ReactElement => {
const props = this.getFieldProps(name, fieldOption)

return (fieldElem: React.ReactElement): React.ReactElement => {
return React.cloneElement(
fieldElem,
props
)
}
}

// 获取表单项数据
private getFieldValue = (name: string): any => {
const field = toJS(this.fields)[name]
return field && field.value
}

// 获取所有表单数据
private getFieldsValue = (): Values => {
const fields = toJS(this.fields)
let values: Values = {}
Object.keys(fields).forEach((name: string): void => {
values[name] = fields[name]
})

return values
}

// 设置表单项的值
private setFieldsValue = (values: Values): void => {
const fields = toJS(this.fields)
Object.keys(values).forEach((name: string): void => {
fields[name].value = values[name]
})
this.fields = fields
}

// 获取用于表单校验的值和规则
private getRulesValues = (name?: string): {rules: Rules, values: Fields} => {
const fields = toJS(this.fields)
const fieldsMeta = toJS(this.fieldsMeta)
const fieldMetaArr: FieldMeta[] = name ? [fieldsMeta[name]] : Object.values(fieldsMeta)
const values: Fields = new Fields()
const rules: Rules = fieldMetaArr.reduce((rules: Rules, item: FieldMeta): Rules => {
if (item.fieldOption.rules.length) {
values[item.name] = fields[item.name].value
return {
...rules,
[item.name]: item.fieldOption.rules
}
}
return rules
}, {})

return {rules, values}
}

// 校验单个表单项
private validateField = (name: string): void => {
const {rules, values} = this.getRulesValues(name)
const validator = new AsyncValidator(rules)

validator.validate(values, {}, (errors: ErrorList): void => {
this.fields[name].errors = []
if (errors) {
errors.forEach((error: ValidateError): void => {
this.fields[name].errors.push(error)
})
}
})
}

// 校验整个表单
private validateFields = (callback?: (errors: ErrorList | null, values: Fields) => void): void => {
const {rules, values} = this.getRulesValues()
const validator = new AsyncValidator(rules)

validator.validate(values, {}, (errors: ErrorList): void => {
Object.keys(values).forEach((name: string): void => {
this.fields[name].errors = []
})
if (errors) {
errors.forEach((error: ValidateError): void => {
this.fields[error.field].errors.push(error)
})
}
callback && callback(errors, values)
})

// 强制渲染组件,避免
this.forceUpdate()
}

// 重置表单
private resetFields = (): void => {
this.fields = Object.values(toJS(this.fieldsMeta)).reduce((fields: Fields, item: FieldMeta): Fields => {
fields[item.name] = new Field()
fields[item.name].value = item.fieldOption.initialValue
return fields
}, new Fields())
}

// 获取表单项的校验结果
private getFieldError = (name: string): ErrorList => {
return this.fields[name] ? this.fields[name].errors : []
}

render() {
let props: FormComponentProps = {
form: {
getFieldDecorator: this.getFieldDecorator,
getFieldValue: this.getFieldValue,
getFieldsValue: this.getFieldsValue,
setFieldsValue: this.setFieldsValue,
validateFields: this.validateFields,
resetFields: this.resetFields,
getFieldError: this.getFieldError,
}
}

return (
<WrappedComponent
ref={this.props.wrappedComponentRef}
{...props}
/>
)
}

}

// 使用hoist-non-react-statics库,复制所有静态方法,请查看:
// https://github.com/mridgway/hoist-non-react-statics
// https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
return hoistStatics(Form, WrappedComponent)
}

export default createForm



使用方法示例代码位置:/src/utils/NewFormModal.tsx



import React from 'react'
import {Input, Select} from 'antd'
import FormItem, {FormItemProps} from 'antd/lib/form/FormItem'
import Modal, {ModalProps} from 'antd/lib/modal'
import createForm, {FormComponentProps, FormProps} from '../utils/createForm'
import {ErrorList, ValidateError} from 'async-validator'

const Option = Select.Option

// FormItem宽度兼容
export const formItemLayout: FormItemProps = {
labelCol: {
xs: {span: 24},
sm: {span: 6}
},
wrapperCol: {
xs: {span: 24},
sm: {span: 16}
}
}
// 性别枚举
enum SexEnum {
male = 'male',
female = 'female'
}

enum SexNameEnum {
male = '男',
female = '女'
}

// 表单字段类型
export class FormModalValues {
username: string = ''
sex: SexEnum = SexEnum.male
}

export interface Props extends ModalProps, FormComponentProps {

}

export class State {
visible: boolean = false
}

export class NewFormModalComponent extends React.Component<Props, State> {

constructor(props: Props) {
super(props)

this.state = new State()
}

// 打开弹窗
public show = (): void => {
this.setState({
visible: true
})
}

// 关闭弹窗
public hide = (): void => {
this.setState({
visible: false
})
}

// 点击确认按钮
public onOk = () => {
// 读取当前表单数据
const values: FormModalValues = this.props.form.getFieldsValue()
console.log(values)

this.props.form.validateFields((errors: any, {username, sex}: FormModalValues) => {
if (!errors) {
Modal.success({
title: '表单输入结果',
content: `用户名:${username},性别:${SexNameEnum[sex]}。`
})
this.hide()
}
})
}

// 关闭弹窗后初始化弹窗参数
public afterClose = (): void => {
this.props.form.resetFields()
this.setState(new State())
}

componentDidMount() {
this.props.form.setFieldsValue(new FormModalValues())
}

render() {
const visible = this.state.visible
const form: FormProps = this.props.form
const username = form.getFieldValue('username')
const sex: SexEnum = form.getFieldValue('sex')
const usernameError: ErrorList = form.getFieldError('username')
const sexError: ErrorList = form.getFieldError('sex')

return (
<Modal
visible={visible}
title={'新建用户'}
onCancel={this.hide}
onOk={this.onOk}
afterClose={this.afterClose}
>
<FormItem
label={'请输入用户名'}
required={true}
validateStatus={usernameError.length ? 'error' : undefined}
help={usernameError.length ? usernameError.map((item: ValidateError) => item.message).join(',') : undefined}
{...formItemLayout}
>
{
form.getFieldDecorator(
'username',
{
initialValue: '',
rules: [
{
required: true,
}
]
}
)(
<Input />
)
}
</FormItem>
<FormItem
label={'请选择性别'}
required={true}
validateStatus={sexError.length ? 'error' : undefined}
help={sexError.length ? sexError.map((item: ValidateError) => item.message).join(',') : undefined}
{...formItemLayout}
>
{
form.getFieldDecorator(
'sex',
{
initialValue: SexEnum.male,
rules: [
{
required: true,
}
]
}
)(
<Select
style={{width: '60px'}}
>
<Option
value={'male'}
>
</Option>
<Option
value={'female'}
>
</Option>
</Select>
)
}
</FormItem>
<FormItem
label={'输入的用户名'}
{...formItemLayout}
>
{username}
</FormItem>
<FormItem
label={'选择的性别'}
{...formItemLayout}
>
{
SexNameEnum[sex]
}
</FormItem>
</Modal>
)
}

}

const NewFormModal = createForm(NewFormModalComponent)

export default NewFormModal



结语



在本文中,我围绕rc-form为你介绍了如下内容:



  1. rc-form表单Demo

  2. 高阶组件(HOC)的实现方式及应用Demo

  3. rc-form源码实现思路及提供部分代码注释

  4. 实现自己的rc-form



希望能够对你理解和使用rc-form有所帮助。



发布于: 2020 年 07 月 09 日 阅读数: 96
用户头像

Lee Chen

关注

还未添加个人签名 2018.08.29 加入

还未添加个人简介

评论

发布
暂无评论
rc-form源码解读