时间:2021-05-26
前言
对于网页系统来说,表单提交是一种很常见的与用户交互的方式,比如提交订单的时候,需要输入收件人、手机号、地址等信息,又或者对系统进行设置的时候,需要填写一些个人偏好的信息。 表单提交是一种结构化的操作,可以通过封装一些通用的功能达到简化开发的目的。本文将讨论Form表单组件设计的思路,并结合有赞的ZentForm组件介绍具体的实现方式。本文所涉及的代码都是基于React v15的版本。
Form组件功能
一般来说,Form组件的功能包括以下几点:
下面将对每个部分的实现方式做详细介绍。
表单布局
常用的表单布局一般有3种方式:
行内布局
水平布局
垂直布局
实现方式比较简单,嵌套css就行。比如form的结构是这样:
<form class="form"> <label class="label"/> <field class="field"/></form>对应3种布局,只需要在form标签增加对应的class:
<!--行内布局--><form class="form inline"> <label class="label"/> <field class="field"/></form><!--水平布局--><form class="form horizontal"> <label class="label"/> <field class="field"/></form><!--垂直布局--><form class="form vertical"> <label class="label"/> <field class="field"/></form>相应的,要定义3种布局的css:
.inline .label { display: inline-block; ...}.inline .field { display: inline-block; ...}.horizontal .label { display: inline-block; ...}.horizontal .field { display: inline-block; ...}.vertical .label { display: block; ...}.vertical .field { display: block; ...}表单字段封装
字段封装部分一般是对组件库的组件针对Form再做一层封装,如Input组件、Select组件、Checkbox组件等。当现有的字段不能满足需求时,可以自定义字段。
表单的字段一般包括两部分,一部分是标题,另一部分是内容。ZentForm通过getControlGroup这一高阶函数对结构和样式做了一些封装,它的入参是要显示的组件:
export default Control => { render() { return ( <div className={groupClassName}> <label className="zent-form__control-label"> {required ? <em className="zent-form__required">*</em> : null} {label} </label> <div className="zent-form__controls"> <Control {...props} {...controlRef} /> {showError && ( <p className="zent-form__error-desc">{props.error}</p> )} {notice && <p className="zent-form__notice-desc">{notice}</p>} {helpDesc && <p className="zent-form__help-desc">{helpDesc}</p>} </div> </div> ); }}这里用到的label和error等信息,是通过Field组件传入的:
<Field label="预约门店:" name="dept" component={CustomizedComp} validations={{ required: true, }} validationErrors={{ required: '预约门店不能为空', }} required/>这里的CustomizedComp是通过getControlGroup封装后返回的组件。
字段与表单之间的交互是一个需要考虑的问题,表单需要知道它包含的字段值,需要在适当的时机对字段进行校验。ZentForm的实现方式是在Form的高阶组件内维护一个字段数组,数组内容是Field的实例。后续通过操作这些实例的方法来达到取值和校验的目的。
ZentForm的使用方式如下:
class FieldForm extends React.Component { render() { return ( <Form> <Field name="name" component={CustomizedComp} </Form> ) }}export default createForm()(FieldForm);其中Form和Field是组件库提供的组件,CustomizedComp是自定义的组件,createForm是组件库提供的高阶函数。在createForm返回的组件中,维护了一个fields的数组,同时提供了attachToForm和detachFromForm两个方法,来操作这个数组。这两个方法保存在context对象当中,Field就能在加载和卸载的时候调用了。简化后的代码如下:
/** * createForm高阶函数 */const createForm = (config = {}) => { ... return WrappedForm => { return class Form extends Component { constructor(props) { super(props); this.fields = []; } getChildContext() { return { zentForm: { attachToForm: this.attachToForm, detachFromForm: this.detachFromForm, } } } attachToForm = field => { if (this.fields.indexOf(field) < 0) { this.fields.push(field); } }; detachFromForm = field => { const fieldPos = this.fields.indexOf(field); if (fieldPos >= 0) { this.fields.splice(fieldPos, 1); } }; render() { return createElement(WrappedForm, {...}); } } }}/** * Field组件 */class Field extends Component { componentWillMount() { this.context.zentForm.attachToForm(this); } componentWillUnmount() { this.context.zentForm.detachFromForm(this); } render() { const { component } = this.props; return createElement(component, {...}); }}当需要获取表单字段值的时候,只需要遍历fields数组,再调用Field实例的相应方法就可以:
/** * createForm高阶函数 */const createForm = (config = {}) => { ... return WrappedForm => { return class Form extends Component { getFormValues = () => { return this.fields.reduce((values, field) => { const name = field.getName(); const fieldValue = field.getValue(); values[name] = fieldValue; return values; }, {}); }; } }}/** * Field组件 */class Field extends Component { getValue = () => { return this.state._value; };}表单验证&错误提示
表单验证是一个重头戏,只有验证通过了才能提交表单。验证的时机也有多种,如字段变更时、鼠标移出时和表单提交时。ZentForm提供了一些常用的验证规则,如非空验证,长度验证,邮箱地址验证等。当然还能自定义一些更复杂的验证方式。自定义验证方法可以通过两种方式传入ZentForm,一种是通过给createForm传参:
createForm({ formValidations: { rule1(values, value){ }, rule2(values, value){ }, }})(FormComp);另一种方式是给Field组件传属性:
<Field validations={{ rule1(values, value){ }, rule2(values, value){ }, }} validationErrors={{ rule1: 'error1', rule2: 'error2' }}/>使用createForm传参的方式,验证规则是共享的,而Field的属性传参是字段专用的。validationErrors指定校验失败后的提示信息。这里的错误信息会显示在前面getControlGroup所定义HTML中{showError && (<p className="zent-form__error-desc">{props.error}</p>)}
ZentForm的核心验证逻辑是createForm的runRules方法,
runRules = (value, currentValues, validations = {}) => { const results = { errors: [], failed: [], }; function updateResults(validation, validationMethod) { // validation方法可以直接返回错误信息,否则需要返回布尔值表明校验是否成功 if (typeof validation === 'string') { results.errors.push(validation); results.failed.push(validationMethod); } else if (!validation) { results.failed.push(validationMethod); } } Object.keys(validations).forEach(validationMethod => { ... // 使用自定义校验方法或内置校验方法(可以按需添加) if (typeof validations[validationMethod] === 'function') { const validation = validations[validationMethod]( currentValues, value ); updateResults(validation, validationMethod); } else { const validation = validationRules[validationMethod]( currentValues, value, validations[validationMethod] ); } }); return results;};默认的校验时机是字段值改变的时候,可以通过Field的validateOnChange和validateOnBlur来改变校验时机。
<Field validateOnChange={false} validateOnBlur={false} validations={{ required: true, matchRegex: /^[a-zA-Z]+$/ }} validationErrors={{ required: '值不能为空', matchRegex: '只能为字母' }}/>对应的,在Field组件中有2个方法来处理change和blur事件:
class Field extends Component { handleChange = (event, options = { merge: false }) => { ... this.setValue(newValue, validateOnChange); ... } handleBlur = (event, options = { merge: false }) => { ... this.setValue(newValue, validateOnBlur); ... } setValue = (value, needValidate = true) => { this.setState( { _value: value, _isDirty: true, }, () => { needValidate && this.context.zentForm.validate(this); } ); };}当触发验证的时候,ZentForm是会对表单对所有字段进行验证,可以通过指定relatedFields来告诉表单哪些字段需要同步进行验证。
表单提交
表单提交时,一般会经历如下几个步骤
ZentForm通过handleSubmit高阶函数定义了上述几个步骤,只需要传入表单提交的逻辑即可:
const handleSubmit = (submit, zentForm) => { const doSubmit = () => { ... result = submit(values, zentForm); ... return result.then( submitResult => { ... if (onSubmitSuccess) { handleOnSubmitSuccess(submitResult); } return submitResult; }, submitError => { ... const error = handleSubmitError(submitError); if (error || onSubmitFail) { return error; } throw submitError; } ); } const afterValidation = () => { if (!zentForm.isValid()) { ... if (onSubmitFail) { handleOnSubmitError(new SubmissionError(validationErrors)); } } else { return doSubmit(); } }; const allIsValidated = zentForm.fields.every(field => { return field.props.validateOnChange || field.props.validateOnBlur; }); if (allIsValidated) { // 不存在没有进行过同步校验的field afterValidation(); } else { zentForm.validateForm(true, afterValidation); }}使用方式如下:
const { handleSubmit } = this.props;<Form onSubmit={handleSubmit(this.submit)} horizontal>ZentForm不足之处
ZentForm虽然功能强大,但仍有一些待改进之处:
结语
本文讨论了Form表单组件设计的思路,并结合有赞的ZentForm组件介绍具体的实现方式。ZentForm的功能十分强大,本文只是介绍了其核心功能,另外还有表单的异步校验、表单的格式化和表单的动态添加删除字段等高级功能都还没涉及到,感兴趣的朋友可点击前面的链接自行研究。
希望阅读完本文后,你对React的Form组件实现有更多的了解,也欢迎留言讨论。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
一、React结合Antd实现登录功能引入所需的Antd组件,代码如下所示:import{Form,Icon,Input,Button,message}from
上篇文章写到了redux实现组件数据共享的方法,但是在react中,redux作者提供了一个更优雅简便的模块实现react组件之间数据共享。那就是利用react
目标exportdefault{data(){return{form:''}}}实现//my-form组件exportdefault{model:{prop:'
reactnative实现登录功能,包括ui的封装、网络请求的封装、导航器的实现、点击事件。demo下载:react-native完整实现登录功能后台如果是sp
vue创建高阶组件的实现不够react优雅,但那是vue和react的设计思想导致的。在react中一切都是函数,而在vue中,组件最终都是函数,但在开发时可以