首頁  >  文章  >  web前端  >  React Form元件封裝實作詳解

React Form元件封裝實作詳解

php中世界最好的语言
php中世界最好的语言原創
2018-05-31 10:28:061351瀏覽

這次帶給大家React Form元件封裝實作詳解,React Form元件封裝實作的注意事項有哪些,以下就是實戰案例,一起來看一下。

前言

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

Form元件功能

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

  1. 表單佈局

  2. 表單欄位

  3. #封裝表單驗證&錯誤提示

  4. 表單提交

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

表單版面表

常用的表單版面配置一般有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: &#39;预约门店不能为空&#39;,
  }}
  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: &#39;error1&#39;,
    rule2: &#39;error2&#39;
  }}
/>

使用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: &#39;值不能为空&#39;,
    matchRegex: &#39;只能为字母&#39;
 }}
/>

对应的,在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的功能十分强大,本文只是介绍了其核心功能,另外还有表单的异步校验、表单的格式化和表单的动态添加删除字段等高级功能都还没涉及到,感兴趣的朋友可点击前面的链接自行研究。

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

怎样使用Vue实现树形视图数据

JS对DOM树实现遍历有哪些方法

以上是React Form元件封裝實作詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn