React Form组件的实现封装杂谈

网友投稿 222 2023-08-05

React Form组件的实现封装杂谈

前言

对于网页系统来说,表单提交是一种很常见的与用户交互的方式,比如提交订单的时候,需要输入收件人、手机号、地址等信息,又或者对系统进行设置的时候,需要填写一些个人偏好的信息。 表单提交是一种结构化的操作,可以通过封装一些通用的功能达到简化开发的目的。本文将讨论Form表单组件设计的思路,并结合有赞的ZentForm组件介绍具体的实现方式。本文所涉及的代码都是基于React v15的版本。

Form组件功能

一般来说,Form组件的功能包括以下几点:

表单布局

表单字段

封装表单验证&错误提示

表单提交

下面将对每个部分的实现方式做详细介绍。

表单布局

常用的表单布局一般有3种方式:

行内布局

水平布局

垂直布局

实现方式比较简单,嵌套css就行。比如form的结构是这样:

对应3种布局,只需要在form标签增加对应的class:

相应的,要定义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 (

{required ? * : null}

{label}

{showError && (

{props.error}

)}

{notice &&

{notice}

{helpDesc &&

{helpDesc}

);

}

}

这里用到的label和error等信息,是通过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 (

label="预约门店:"

name="dept"

component={CustomizedComp}

validations={{

required: true,

}}

validationErrors={{

required: '预约门店不能为空',

}}

required

/>

这里的CustomizedComp是通过getControlGroup封装后返回的组件。

字段与表单之间的交互是一个需要考虑的问题,表单需要知道它包含的字段值,需要在适当的时机对字段进行校验。ZentForm的实现方式是在Form的高阶组件内维护一个字段数组,数组内容是Field的实例。后续通过操作这些实例的方法来达到取值和校验的目的。

ZentForm的使用方式如下:

class FieldForm extends React.Component {

render() {

return (

name="name"

component={CustomizedComp}

)

}

}

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提供了一些常用的验证规则,如非空验证,长度验证,邮箱地址验证等。当然还能自定义一些更复http://杂的验证方式。自定义验证方法可以通过两种方式传入Zenhttp://tForm,一种是通过给createForm传参:

createForm({

formValidations: {

rule1(values, value){

},

rule2(values, value){

},

}

})(FormComp);

另一种方式是给Field组件传属性:

validations={{

rule1(values, value){

},

rule2(values, value){

},

}}

validationErrors={{

rule1: 'error1',

rule2: 'error2'

}}

/>

使用createForm传参的方式,验证规则是共享的,而Field的属性传参是字段专用的。validationErrors指定校验失败后的提示信息。这里的错误信息会显示在前面getControlGroup所定义HTML中{showError && (

{props.error}

)}

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来改变校验时机。

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;

ZentForm不足之处

ZentFhttp://orm虽然功能强大,但仍有一些待改进之处:

父组件维护了所有字段的实例,直接调用实例的方法来取值或者验证。这种方式虽然简便,但有违React声明式编程和函数式编程的设计思想,并且容易产生副作用,在不经意间改变了字段的内部属性。

大部分的组件重使用了shouldComponentUpdate,并对state和props进行了深比较,对性能有比较大的影响,可以考虑使用PureComponent。

太多的情况下对整个表单字段进行了校验,比较合理的情况应该是某个字段修改的时候只校验本身,在表单提交时再校验所有的字段。

表单提交操作略显繁琐,还需要调用一次handleSubmit,不够优雅。

结语

本文讨论了Form表单组件设计的思路,并结合有赞的ZentForm组件介绍具体的实现方式。ZentForm的功能十分强大,本文只是介绍了其核心功能,另外还有表单的异步校验、表单的格式化和表单的动态添加删除字段等高级功能都还没涉及到,感兴趣的朋友可点击前面的链接自行研究。

希望阅读完本文后,你对React的Form组件实现有更多的了解,也欢迎留言讨论。

name="name"

component={CustomizedComp}

)

}

}

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提供了一些常用的验证规则,如非空验证,长度验证,邮箱地址验证等。当然还能自定义一些更复http://杂的验证方式。自定义验证方法可以通过两种方式传入Zenhttp://tForm,一种是通过给createForm传参:

createForm({

formValidations: {

rule1(values, value){

},

rule2(values, value){

},

}

})(FormComp);

另一种方式是给Field组件传属性:

validations={{

rule1(values, value){

},

rule2(values, value){

},

}}

validationErrors={{

rule1: 'error1',

rule2: 'error2'

}}

/>

使用createForm传参的方式,验证规则是共享的,而Field的属性传参是字段专用的。validationErrors指定校验失败后的提示信息。这里的错误信息会显示在前面getControlGroup所定义HTML中{showError && (

{props.error}

)}

validations={{

rule1(values, value){

},

rule2(values, value){

},

}}

validationErrors={{

rule1: 'error1',

rule2: 'error2'

}}

/>

使用createForm传参的方式,验证规则是共享的,而Field的属性传参是字段专用的。validationErrors指定校验失败后的提示信息。这里的错误信息会显示在前面getControlGroup所定义HTML中{showError && (

{props.error}

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来改变校验时机。

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;

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;

ZentForm不足之处

ZentFhttp://orm虽然功能强大,但仍有一些待改进之处:

父组件维护了所有字段的实例,直接调用实例的方法来取值或者验证。这种方式虽然简便,但有违React声明式编程和函数式编程的设计思想,并且容易产生副作用,在不经意间改变了字段的内部属性。

大部分的组件重使用了shouldComponentUpdate,并对state和props进行了深比较,对性能有比较大的影响,可以考虑使用PureComponent。

太多的情况下对整个表单字段进行了校验,比较合理的情况应该是某个字段修改的时候只校验本身,在表单提交时再校验所有的字段。

表单提交操作略显繁琐,还需要调用一次handleSubmit,不够优雅。

结语

本文讨论了Form表单组件设计的思路,并结合有赞的ZentForm组件介绍具体的实现方式。ZentForm的功能十分强大,本文只是介绍了其核心功能,另外还有表单的异步校验、表单的格式化和表单的动态添加删除字段等高级功能都还没涉及到,感兴趣的朋友可点击前面的链接自行研究。

希望阅读完本文后,你对React的Form组件实现有更多的了解,也欢迎留言讨论。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Spring MVC整合Shiro权限控制的方法
下一篇:浅谈Vue响应式(数组变异方法)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~