>  기사  >  웹 프론트엔드  >  React Form을 사용하여 컴포넌트 캡슐화를 완료하는 방법

React Form을 사용하여 컴포넌트 캡슐화를 완료하는 방법

php中世界最好的语言
php中世界最好的语言원래의
2018-05-23 13:43:122032검색

이번에는 React Form을 사용하여 컴포넌트 패키징을 완성하는 방법과 React Form을 사용하여 컴포넌트 패키징을 완성하기 위한 주의 사항에 대해 설명하겠습니다. 아래는 실제 사례입니다.

머리말

웹 시스템의 경우 양식 제출은 사용자와 상호 작용하는 매우 일반적인 방법입니다. 예를 들어 주문을 제출할 때 수신자, 휴대폰 번호, 주소 및 기타 정보를 입력해야 합니다. 시스템을 설정할 때 일부 개인 기본 설정 정보를 입력해야 합니다. 양식 제출은 몇 가지 공통 기능을 캡슐화하여 개발을 단순화할 수 있는 구조화된 작업입니다. 이번 글에서는 Formform 컴포넌트의 디자인 아이디어에 대해 논의하고 ZentForm 컴포넌트와 결합한 구체적인 구현 방법을 소개합니다. 이 글에 포함된 코드는 React v15를 기반으로 합니다.

양식 구성 요소 기능

일반적으로 양식 구성 요소의 기능에는 다음 사항이 포함됩니다.

  1. 양식 레이아웃

  2. 양식 필드

  3. 캡슐화 양식 유효성 검사 오류 메시지

  4. 양식 제출

각 부분의 구현은 아래에서 자세히 소개하겠습니다.

양식 레이아웃

일반적으로 사용되는 양식 레이아웃에는 3가지 방법이 있습니다.

인라인 레이아웃

가로 레이아웃

세로 레이아웃

실현 방법은 상대적으로 간단합니다. 단지 CSS를 중첩하면 됩니다. 예를 들어 양식의 구조는 다음과 같습니다.

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

는 3개 레이아웃에 해당합니다. 양식 태그에 해당 클래스만 추가하면 됩니다.

<!--行内布局-->
<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;
  ...
}

양식 필드 캡슐화

필드 캡슐화 부분은 일반적으로 입력 구성 요소, 선택 구성 요소, 확인란 구성 요소 등과 같은 양식용 구성 요소 라이브러리의 구성 요소를 캡슐화합니다. 기존 필드가 요구 사항을 충족할 수 없는 경우 필드를 사용자 정의할 수 있습니다.

양식 필드는 일반적으로 두 부분으로 구성됩니다. 하나는 제목이고 다른 하나는 내용입니다. 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: &#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에 의해 반환된 구성 요소에는 필드 배열이 유지되며 이 배열을 작동하기 위해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, {...});
  }
}

양식 필드 값을 가져와야 하는 경우 필드 배열을 순회한 다음 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怎么实现数组去重算法

위 내용은 React Form을 사용하여 컴포넌트 캡슐화를 완료하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.