国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > JS > 正文

React Form組件的實現封裝雜談

2024-05-06 16:44:07
字體:
來源:轉載
供稿:網友

前言

對于網頁系統來說,表單提交是一種很常見的與用戶交互的方式,比如提交訂單的時候,需要輸入收件人、手機號、地址等信息,又或者對系統進行設置的時候,需要填寫一些個人偏好的信息。 表單提交是一種結構化的操作,可以通過封裝一些通用的功能達到簡化開發的目的。本文將討論Form表單組件設計的思路,并結合有贊的ZentForm組件介紹具體的實現方式。本文所涉及的代碼都是基于React v15的版本。

Form組件功能

一般來說,Form組件的功能包括以下幾點:

  1. 表單布局
  2. 表單字段
  3. 封裝表單驗證&錯誤提示
  4. 表單提交

下面將對每個部分的實現方式做詳細介紹。

表單布局

常用的表單布局一般有3種方式:

行內布局

React,Form組件,Form

水平布局

React,Form組件,Form

垂直布局

React,Form組件,Form

實現方式比較簡單,嵌套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的validateOnChangevalidateOnBlur來改變校驗時機。

<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來告訴表單哪些字段需要同步進行驗證。

表單提交

表單提交時,一般會經歷如下幾個步驟

  1. 表單驗證
  2. 表單提交
  3. 提交成功處理
  4. 提交失敗處理

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雖然功能強大,但仍有一些待改進之處:

  1. 父組件維護了所有字段的實例,直接調用實例的方法來取值或者驗證。這種方式雖然簡便,但有違React聲明式編程和函數式編程的設計思想,并且容易產生副作用,在不經意間改變了字段的內部屬性。
  2. 大部分的組件重使用了shouldComponentUpdate,并對state和props進行了深比較,對性能有比較大的影響,可以考慮使用PureComponent。
  3. 太多的情況下對整個表單字段進行了校驗,比較合理的情況應該是某個字段修改的時候只校驗本身,在表單提交時再校驗所有的字段。
  4. 表單提交操作略顯繁瑣,還需要調用一次handleSubmit,不夠優雅。

結語

本文討論了Form表單組件設計的思路,并結合有贊的ZentForm組件介紹具體的實現方式。ZentForm的功能十分強大,本文只是介紹了其核心功能,另外還有表單的異步校驗、表單的格式化和表單的動態添加刪除字段等高級功能都還沒涉及到,感興趣的朋友可點擊前面的鏈接自行研究。

希望閱讀完本文后,你對React的Form組件實現有更多的了解,也歡迎留言討論。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 济宁市| 迭部县| 阜新市| 黑河市| 石首市| 罗田县| 甘孜县| 周宁县| 靖西县| 高邮市| 富锦市| 五峰| 宜都市| 泸溪县| 白银市| 滦平县| 赞皇县| 丹寨县| 平邑县| 文安县| 专栏| 深泽县| 新沂市| 白玉县| 东兴市| 和硕县| 晋中市| 敦煌市| 花莲县| 峨山| 镇坪县| 丹凤县| 封丘县| 江华| 德州市| 丹巴县| 邛崃市| 临猗县| 山丹县| 汉川市| 金阳县|