前言
在选择使用 react 技术栈的时,往往都会选择一套成熟好用而且自己熟悉的 UI 框架,而我使用比较多的ant Design,这样可以减少开发成本与风险,因为自己开发的组件总会有考虑不周的 BUG,而 antd 里有很多优秀的组件,但我觉得在表单方面,antd 的这个组件做的很优秀,也很灵活,它的核心也在校验,这篇主要是 Form 校验。
表单作用
真正需要表单的时候,一般都因为以下功能:
- 收集各表单项的数据。
- 按照要求,对表单项数据进行校验,并显示校验结果。
- 最总获取所有的表单数据,并表单中所有数据进行校验。
而这些功能 antd 都可以直接按官网案例使用,简单快速,但 antd 实现这些功能也是很简单的,主要是因为它把数据收集与校验都交给了 rc-form 去实现,剩下的 UI 展示才是 antd 实现的。
Form.tsx
在 form 的 Form.tsx 只是简单的实现了 Form 的 UI 与 rc-form 的 create 表单,使用 createReactContext 传递了通用布局属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| // 此处为Form.create的实现,主要调用了createBaseForm // fieldMetaProp规定了重造组件里存储的表单规则,如校验等,fieldDataProp规定了表单的数据,所以获取到react组件,就可以知道表单属性设置与数据
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, }); };
// 创建表单与createReactContext传递UI布局属性
render() { const { wrapperCol, labelAlign, labelCol, layout, colon } = this.props; return ( <FormContext.Provider value={{ wrapperCol, labelAlign, labelCol, vertical: layout === 'vertical', colon }} > <ConfigConsumer>{this.renderForm}</ConfigConsumer> </FormContext.Provider> ); }
|
FormItem.tsx
实现了需要校验组件的布局与校验信息显示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| // 通过这两个方法获取到组件上的所有表单信息,如值、校验信息,获取的方式就是在组件的props的FIELD_META_PROP与FIELD_DATA_PROP上。 getMeta() { return this.getChildProp(FIELD_META_PROP); }
getField() { return this.getChildProp(FIELD_DATA_PROP); }
// 通过getValidateStatus获取校验的状态
getValidateStatus() { const onlyControl = this.getOnlyControl(); if (!onlyControl) { return ''; } const field = this.getField(); if (field.validating) { return 'validating'; } if (field.errors) { return 'error'; } const fieldValue = 'value' in field ? field.value : this.getMeta().initialValue; if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') { return 'success'; } return ''; }
// renderValidateWrapper根据校验状态,选择html结构与css类名,显示校验信息
renderValidateWrapper( prefixCls: string, c1: React.ReactNode, c2: React.ReactNode, c3: React.ReactNode, ) { const { props } = this; const onlyControl = this.getOnlyControl; const validateStatus = props.validateStatus === undefined && onlyControl ? this.getValidateStatus() : props.validateStatus;
let classes = `${prefixCls}-item-control`; if (validateStatus) { classes = classNames(`${prefixCls}-item-control`, { 'has-feedback': props.hasFeedback || validateStatus === 'validating', 'has-success': validateStatus === 'success', 'has-warning': validateStatus === 'warning', 'has-error': validateStatus === 'error', 'is-validating': validateStatus === 'validating', }); }
let iconType = ''; switch (validateStatus) { case 'success': iconType = 'check-circle'; break; case 'warning': iconType = 'exclamation-circle'; break; case 'error': iconType = 'close-circle'; break; case 'validating': iconType = 'loading'; break; default: iconType = ''; break; }
const icon = props.hasFeedback && iconType ? ( <span className={`${prefixCls}-item-children-icon`}> <Icon type={iconType} theme={iconType === 'loading' ? 'outlined' : 'filled'} /> </span> ) : null;
return ( <div className={classes}> <span className={`${prefixCls}-item-children`}> {c1} {icon} </span> {c2} {c3} </div> ); }
|
所以使用 antd Form 需要把组件放到 Form 与 FormItem 里,否则会出现 UI 问题。
rc-form 的实现是使用了高阶组件,这里不说高阶组件,然后利用闭包的收集数据,类似与全局变量,创建一个对象,根据 ID 收集放到对应的 ID 里,简单就是这样。
源码主要看 createBaseForm 与 createFieldsStore,createBaseForm 主要是实现功能,如校验,获取数据等等,而 createFieldsStore 主要实现数据收集与处理,然后让 createBaseForm 使用。
createBaseForm 数据的更新是使用了 forceUpdate,这是 react 强制更新组件的方法,可以在没有触发 setState 时触发 render 来实现数据更新,它的实现与 setState 差不多。
mapPropsToFields 的实现:
1 2 3 4 5 6 7 8 9 10 11 12
| // 初始化 getInitialState() { const fields = mapPropsToFields && mapPropsToFields(this.props); this.fieldsStore = createFieldsStore(fields || {}); ... } // 父组件state变化 componentWillReceiveProps(nextProps) { if (mapPropsToFields) { this.fieldsStore.updateFields(mapPropsToFields(nextProps)); } }
|
由于在创建组件时都用 Form 包含,所以组件之间需要经过 Form,那么就可以利用这个实现数据映射关系。
所有的校验都会统一走 validateFieldsInternal 方法,而一般的回填与映射是不会触发校验的,只有符合事件的或手动调用校验才会触发校验,而校验使用的async-validator这个包。
注意事项:
- 由于 rc-form 的 ID 允许嵌套,所以 ID 避免实现”.”与[,如 abc.mm,abc[0]
- rc-form 校验的 type 为 Array 时,避免 ID 带数字,如 abc111m1
具体实现可以查看资料
最后更新时间: