在开发过程当中,进行表单校验是一个很经常使用的功能。html
表单校验一般须要实现如下几个功能:react
收集各表单项的数据,如Input输入框,Select选择框等。git
按照需求,对表单项数据进行校验,并显示校验结果。github
须要提交表单时,对表单中全部数据进行校验,并收集全部数据。redux
这些功能看似简单,本身实现的话,仍是会产生很多问题。所以,最好使用已有的库来实现此功能。我在开发中一般使用Ant Design的Form组件。从文档中的介绍能够看出,Form组件的功能实现主要是引用了rc-form。数组
rc-form在开发中帮了我很多忙,我对它的实现方式也很感兴趣,因而研究了它的源码,如今与你们分享一下。bash
我将为你梳理rc-form的主要实现思路,为你讲解rc-form源码中部分方法的用途,并提供部分代码的注释。antd
最后,我还会本身实现一个精简版的rc-form组件,供你参考。架构
本文中的Demo使用TypeScript编写,若是你对TypeScript不了解,能够查看TypeScript文档。但只要有JavaScript基础,就不会影响阅读。app
为了方便理解,你还能够从GitHub下载Ant Design和rc-form的源码。
获取文中示例项目代码,请点击这里。
Demo运行方法:
$ yarn install
$ yarn start # visit http://localhost:3000/
复制代码
运行后你会看到一个这样的Demo页面:
下图是一个rc-form表单Demo,你能够在http://localhost:3000/页面中,点击表单弹窗
按钮,查看效果。
这个表单实现了以下功能:
在用户输入时和点击确认按钮时,会进行表单项的非空校验。
用户输入和选择的结果,会显示在表单下方。
点击确认按钮,若校验经过,会弹窗提示用户输入结果。
该表单是使用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中,咱们主要用到了如下几个方法:
Form.create:建立一个新的表单组件,提供表单校验、获取数据等方法,以及存储表单数据功能。
this.props.form.getFieldDecorator:为表单字段绑定value和onChange等事件,并实现校验等功能。
this.props.form.getFieldValue:获取表单字段值。
this.props.form.setFieldsValue:为表单字段设置值。
this.props.form.validateFields:进行表单校验,并返回校验结果和当前表单数据。
this.props.form.resetFields:重置表单数据为初始值。
上面列出的方法中,除了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,
});
};
复制代码
查看rc-form源码,能够看到createDOMForm
方法仅仅是调用了createBaseForm
方法。
createDOMForm示例代码位置:
/rc-form/src/createDOMForm.js
function createDOMForm(option) {
return createBaseForm({
...option,
}, [mixin]);
}
复制代码
如今咱们的重点应当放在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)。
咱们如今已经知道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)有如下特色:
高阶组件是对已有组件的封装,造成了一个新组件,新组件实现了特定的业务逻辑,并将其经过props传给原有组件。
高阶组件一般不须要实现UI,其UI由传入的原组件实现,它只是为原组件提供了额外的功能或数据。
下面来看一个简单的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属性,能够获取到原组件。
下面是一个使用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
复制代码
下面是一个使用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
复制代码
有了HOC的知识做为铺垫,咱们就能够正式进入rc-form源码解读了。
开始正式的解读以前,我先说说我我的对于阅读源码的意见,以rc-form为例,它实现的功能虽然不是十分复杂,但因为这是一个要提供给不少人使用的库,为了不出错,就须要进行不少的校验,以及开发环境提示等等。虽然这些都是必要的,但却会致使代码十分冗长,若是对代码不熟悉的话,会有不小的阅读障碍。
所以,我我的认为在阅读源码的时候,能够把重点放在如下两个方面:
理解做者进行开发的思路。就如同谈Redux的时候,都要了解的Flux架构。建议在阅读源码的时候,重点放在理解做者“为何要这么作”,而不是研究做者是如何实现某个功能的。
学习做者的优秀习惯、技巧。上面说重点要理解做者的思路,但并非让你放弃关注细节,而是要有取舍地看。一些本身彻底有能力实现,或者做者只是在作一些报错提示之类的代码,能够直接跳过。固然若是看到做者的一些优秀习惯、技巧,或者是一些本身没有想过的实现方式,仍是颇有必要借鉴的。
我梳理了rc-form的实现思路,供你们参考,本次源码解读会按照下图进行讲解。建议你在查看rc-form源码时,时常对照这张图,这样更加便于理解。
在以前的高阶组件(HOC)讲解中,已经解读过createBaseForm
方法的实现方式,这里就再也不赘述。
接下来将依次以createBaseForm
中的各个方法,讲解一下rc-form的实现逻辑,每段代码解读都会提供该段代码实现的主要功能,为方便理解,在代码中也提供了部分注释。
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方法实现了组装新组件须要的props属性与方法,并将其传入新组件,这是一个普通的高阶组件实现方式。
新组件的props主要来自于createBaseForm.js
和createForm.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),它主要为新组件增长了绑定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;
}
复制代码
createBaseForm.js
中并未实现getFieldsValue
、getFieldValue
方法,而是直接调用了this.fieldsStore.getFieldsValue
、this.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
方法,实现的就是设置表单数据的功能,代码按以下流程调用:
this.setFieldsValue
→ this.setFields
→ this.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
方法,实现了重置表单为初始值功能。它的实现方式是:
先调用this.fieldsStore.resetFields
方法,清空全部表单数据。
调用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;
}, {});
}
复制代码
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;
}
复制代码
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的实现思路其实不复杂,分别使用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为你介绍了以下内容:
但愿可以对你理解和使用rc-form有所帮助。