欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React Form組件的實現(xiàn)封裝雜談

 更新時間:2018年05月07日 09:50:14   作者:dabai  
這篇文章主要介紹了React Form組件的實現(xiàn)封裝雜談,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

前言

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

Form組件功能

一般來說,F(xiàn)orm組件的功能包括以下幾點:

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

下面將對每個部分的實現(xiàn)方式做詳細介紹。

表單布局

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

行內(nèi)布局

水平布局

垂直布局

實現(xiàn)方式比較簡單,嵌套css就行。比如form的結(jié)構(gòu)是這樣:

<form class="form">
  <label class="label"/>
  <field class="field"/>
</form>

對應(yīng)3種布局,只需要在form標簽增加對應(yīng)的class:

<!--行內(nèi)布局-->
<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>

相應(yīng)的,要定義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組件等。當現(xiàn)有的字段不能滿足需求時,可以自定義字段。

表單的字段一般包括兩部分,一部分是標題,另一部分是內(nèi)容。ZentForm通過getControlGroup這一高階函數(shù)對結(jié)構(gòu)和樣式做了一些封裝,它的入?yún)⑹且@示的組件:

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="預(yù)約門店:"
  name="dept"
  component={CustomizedComp}
  validations={{
    required: true,
  }}
  validationErrors={{
    required: '預(yù)約門店不能為空',
  }}
  required
/>

這里的CustomizedComp是通過getControlGroup封裝后返回的組件。

字段與表單之間的交互是一個需要考慮的問題,表單需要知道它包含的字段值,需要在適當?shù)臅r機對字段進行校驗。ZentForm的實現(xiàn)方式是在Form的高階組件內(nèi)維護一個字段數(shù)組,數(shù)組內(nèi)容是Field的實例。后續(xù)通過操作這些實例的方法來達到取值和校驗的目的。

ZentForm的使用方式如下:

class FieldForm extends React.Component {
  render() {
    return (
      <Form>
        <Field
          name="name"
          component={CustomizedComp}
      </Form>
    )
  }
}

export default createForm()(FieldForm);

其中Form和Field是組件庫提供的組件,CustomizedComp是自定義的組件,createForm是組件庫提供的高階函數(shù)。在createForm返回的組件中,維護了一個fields的數(shù)組,同時提供了attachToForm和detachFromForm兩個方法,來操作這個數(shù)組。這兩個方法保存在context對象當中,F(xiàn)ield就能在加載和卸載的時候調(diào)用了。簡化后的代碼如下:

/**
 * createForm高階函數(shù)
 */
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數(shù)組,再調(diào)用Field實例的相應(yīng)方法就可以:

/**
 * createForm高階函數(shù)
 */
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提供了一些常用的驗證規(guī)則,如非空驗證,長度驗證,郵箱地址驗證等。當然還能自定義一些更復(fù)雜的驗證方式。自定義驗證方法可以通過兩種方式傳入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傳參的方式,驗證規(guī)則是共享的,而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 => {
    ...
    // 使用自定義校驗方法或內(nèi)置校驗方法(可以按需添加)
    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: '只能為字母'
 }}
/>

對應(yīng)的,在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);
      }
    );
 };
}

當觸發(fā)驗證的時候,ZentForm是會對表單對所有字段進行驗證,可以通過指定relatedFields來告訴表單哪些字段需要同步進行驗證。

表單提交

表單提交時,一般會經(jīng)歷如下幾個步驟

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

ZentForm通過handleSubmit高階函數(shù)定義了上述幾個步驟,只需要傳入表單提交的邏輯即可:

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

結(jié)語

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

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

相關(guān)文章

  • React項目中hook實現(xiàn)展示對話框功能

    React項目中hook實現(xiàn)展示對話框功能

    Modal(模態(tài)框)是 web 開發(fā)中十分常見的組件,即從頁面中彈出的對話框,下面這篇文章主要給大家介紹了關(guān)于React項目中hook實現(xiàn)展示對話框功能的相關(guān)資料,需要的朋友可以參考下
    2022-05-05
  • react.js組件實現(xiàn)拖拽復(fù)制和可排序的示例代碼

    react.js組件實現(xiàn)拖拽復(fù)制和可排序的示例代碼

    這篇文章主要介紹了react.js組件實現(xiàn)拖拽復(fù)制和可排序的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • 詳解React中組件之間通信的方式

    詳解React中組件之間通信的方式

    這篇文章主要介紹了React中組件之間通信的方式,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-07-07
  • React使用useImperativeHandle自定義暴露給父組件的示例詳解

    React使用useImperativeHandle自定義暴露給父組件的示例詳解

    useImperativeHandle?是?React?提供的一個自定義?Hook,用于在函數(shù)組件中顯式地暴露給父組件特定實例的方法,本文將介紹?useImperativeHandle的基本用法、常見應(yīng)用場景,需要的可以參考下
    2024-03-03
  • React復(fù)制到剪貼板的示例代碼

    React復(fù)制到剪貼板的示例代碼

    本篇文章主要介紹了React復(fù)制到剪貼板的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • react實現(xiàn)導(dǎo)航欄二級聯(lián)動

    react實現(xiàn)導(dǎo)航欄二級聯(lián)動

    這篇文章主要為大家詳細介紹了react實現(xiàn)導(dǎo)航欄二級聯(lián)動,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • react中useRef的應(yīng)用使用詳解

    react中useRef的應(yīng)用使用詳解

    這篇文章主要介紹了react中useRef的應(yīng)用使用詳解的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • 使用 React 和 Threejs 創(chuàng)建一個VR全景項目的過程詳解

    使用 React 和 Threejs 創(chuàng)建一個VR全景項目的過程詳解

    這篇文章主要介紹了使用 React 和 Threejs 創(chuàng)建一個VR全景項目的過程詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • 基于React實現(xiàn)表單數(shù)據(jù)的添加和刪除詳解

    基于React實現(xiàn)表單數(shù)據(jù)的添加和刪除詳解

    這篇文章主要給大家介紹了基于React實現(xiàn)表單數(shù)據(jù)的添加和刪除的方法,文中給出了詳細的示例供大家參考,相信對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。
    2017-03-03
  • React Router V6更新內(nèi)容詳解

    React Router V6更新內(nèi)容詳解

    這篇文章主要為大家介紹了React Router V6更新內(nèi)容,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-12-12

最新評論