今回は、React Form を使用してコンポーネントのパッケージ化を完了する方法と、React Form を使用してコンポーネントのパッケージ化を完了するための注意事項について説明します。以下に実際のケースを示します。
はじめに
Web システムの場合、フォーム送信はユーザーと対話するための非常に一般的な方法です。たとえば、注文を送信するときに、受信者、携帯電話番号、住所、その他の情報を入力する必要があります。システムをセットアップするときは、個人設定情報を入力する必要があります。 フォーム送信は、いくつかの共通機能をカプセル化することで開発を簡素化できる構造化された操作です。この記事では、Formフォームコンポーネントの設計思想を解説し、ZentFormコンポーネントと組み合わせた具体的な実装方法を紹介します。この記事に含まれるコードは React v15 に基づいています。
フォームコンポーネントの機能
一般的に、フォームコンポーネントの機能には次の点が含まれます:フォーム検証 & エラープロンプト
フォームのレイアウト
よく使われるフォームのレイアウトには大きく分けて3つの方法があります。ティカルレイアウト実現方法は比較的シンプルで、CSSをネストするだけです。たとえば、フォームの構造は次のとおりです:
<form class="form"> <label class="label"/> <field class="field"/> </form>
<!--行内布局--> <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;
...
}
フォームフィールドのカプセル化
フィールドカプセル化部分は通常、入力コンポーネント、選択コンポーネント、チェックボックスコンポーネントなど、フォームのコンポーネントライブラリのコンポーネントをカプセル化します。既存のフィールドがニーズを満たせない場合は、フィールドをカスタマイズできます。
フォームのフィールドには通常 2 つの部分が含まれており、1 つはタイトル、もう 1 つはコンテンツです。 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> ); } }
<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 によって返されるコンポーネントでは、フィールドの配列が維持され、この配列を操作するための 2 つのメソッド、attachToForm および detachFromForm が提供されます。これら 2 つのメソッドはコンテキスト オブジェクトに保存され、ロードおよびアンロード時に 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, {...});
}
}
/**
* 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 中国語 Web サイトの他の関連記事を参照してください。