這次帶給大家怎樣用React Form完成元件封裝,用React Form完成元件封裝的注意事項有哪些,下面就是實戰案例,一起來看一下。
前言
對於網頁系統來說,表單提交是一種很常見的與使用者互動的方式,例如提交訂單的時候,需要輸入收件人、手機號碼、地址等信息,又或者對系統進行設定的時候,需要填寫一些個人偏好的信息。表單提交是一種結構化的操作,可以透過封裝一些通用的功能來達到簡化開發的目的。本文將討論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 ( <p className={groupClassName}> <label className="zent-formcontrol-label"> {required ? <em className="zent-formrequired">*</em> : null} {label} </label> <p className="zent-formcontrols"> <Control {...props} {...controlRef} /> {showError && ( <p className="zent-formerror-desc">{props.error}</p> )} {notice && <p className="zent-formnotice-desc">{notice}</p>} {helpDesc && <p className="zent-formhelp-desc">{helpDesc}</p>} </p> </p> ); } }這裡用到的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-formerror-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的validate<a href="http://www.php.cn/wiki/1464.html" target="_blank">OnChange</a>
和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虽然功能强大,但仍有一些待改进之处:
父组件维护了所有字段的实例,直接调用实例的方法来取值或者验证。这种方式虽然简便,但有违React声明式编程和函数式编程的设计思想,并且容易产生副作用,在不经意间改变了字段的内部属性。
大部分的组件重使用了shouldComponentUpdate,并对state和props进行了深比较,对性能有比较大的影响,可以考虑使用PureComponent。
太多的情况下对整个表单字段进行了校验,比较合理的情况应该是某个字段修改的时候只校验本身,在表单提交时再校验所有的字段。
表单提交操作略显繁琐,还需要调用一次handleSubmit,不够优雅。
结语
本文讨论了Form表单组件设计的思路,并结合有赞的ZentForm组件介绍具体的实现方式。ZentForm的功能十分强大,本文只是介绍了其核心功能,另外还有表单的异步校验、表单的格式化和表单的动态添加删除字段等高级功能都还没涉及到,感兴趣的朋友可点击前面的链接自行研究。
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
以上是怎樣用React Form完成元件封裝的詳細內容。更多資訊請關注PHP中文網其他相關文章!