Application of Form in data stack: in depth

The topic of this article is a process of understanding and digesting the Form after it is used in several stacks of products. By introducing some forms
The methods commonly used in the Form to understand some design ideas and deepen our pursuit of technology. This paper mainly introduces the creation of Form and the two-way binding of Form (getFieldDecorator).

The Form forms mentioned later are antd 3 X, hereinafter referred to as Form form. stay Application of Form in data stack (Part I): Verification It is mentioned in that we were born in the best era. In fact, others made wheels and helped us do some things. Let's take a look at how other people made wheels and whether we can realize them ourselves. Students who have paid attention to Antd may be impressed that Antd is UI encapsulated based on react component components, and the article will focus on the code of react component / form.

1, Someone else's Form

1.1 From.create

First check the createform JS file, which is mainly for createbaseform JS file is encapsulated in one layer, and some common methods are added.

import createBaseForm from './createBaseForm';
​
export const mixin = {
  getForm() {
    return {
      getFieldsValue: this.fieldsStore.getFieldsValue,
      getFieldValue: this.fieldsStore.getFieldValue,
      ...
      validateFields: this.validateFields,
      resetFields: this.resetFields,
    };
  },
};
​
function createForm(options) {
  return createBaseForm(options, [mixin]);
}
​
export default createForm;

Next, take a look at createBaseForm JS file, mainly to view the createBaseForm method in this file. This method acts as a decorator. A variable with the default form is wrapped in props, in which all functions of form are completed. The function of createBaseForm is to copy the currently passed component, that is, call the function to pass the current component as a wrapped component, and finally return a wrapped component with new attributes.

render() {
  const { wrappedComponentRef, ...restProps } = this.props; // eslint-disable-line
  const formProps = {
    // The getForm method comes from createform JS, wrap a formPropName variable in props, and the default is form
    [formPropName]: this.getForm(),
  };
  // Gets an instance of the form
  if (withRef) {
    formProps.ref = 'wrappedComponent';
  } else if (wrappedComponentRef) {
    formProps.ref = wrappedComponentRef;
  }
  const props = mapProps.call(this, {
    ...formProps,
    ...restProps,
  });
  return <WrappedComponent {...props} />;
}

decorator: it is a class related syntax, which is mainly used to modify classes and class methods (class attributes). Most object-oriented programming languages support this syntax, such as Java and Python. decorator can be simply understood as: it can modify some objects and then return a wrapped object.

Overall, Form Create (options) actually encapsulates our business components, initializes the Form related attributes, mounts some methods that need to be used, and adds these methods to props Next.

1.2 getFieldDecorator

<FormItem {...formItemLayout} label="full name" >
  {getFieldDecorator('name', {
    initialValue: userInfo.name,
    rules: [
      { required: true, message: 'Name cannot be empty!' }
    ]
  })(
    <Input placeholder="Please enter your name" />
  )}
</FormItem>

As can be seen from the above use code and the implementation method below, getFieldDecorator is a coriolised function. Through the input of id and parameters, it returns a Dom node that takes the input component as the input to participate in the new attribute, and mounts various props such as valuePropName, getValueProps, initialValue and rules of option to the input component.

getFieldDecorator(name, fieldOption) {
  const props = this.getFieldProps(name, fieldOption);
  return fieldElem => {
    // We should put field in record if it is rendered
    this.renderFields[name] = true;
​
    const fieldMeta = this.fieldsStore.getFieldMeta(name);
    const originalProps = fieldElem.props;
    fieldMeta.originalProps = originalProps;
    fieldMeta.ref = fieldElem.ref;
    const decoratedFieldElem = React.cloneElement(fieldElem, {
      ...props,
      // It is undefined if there is no initialValue, and it is the value of initialValue if there is
      ...this.fieldsStore.getFieldValuePropValue(fieldMeta),
    });
    return supportRef(fieldElem) ? (
      decoratedFieldElem
    ) : (
      <FieldElemWrapper name={name} form={this}>
        {decoratedFieldElem}
      </FieldElemWrapper>
    );
  };
}

getFieldDecorator has the following two functions, which can be found in createbaseform The getFieldProps and getFieldValuePropValue methods of JS file are verified respectively:

  • When initializing the data field, put the data field into fieldsStore;
  • When props is mounted on the input component, the data fields are read from fieldsStore.

1.3 validateFields

We usually use the validateFields method to verify our form data. See createbaseform After the implementation of the validateFields method in the. JS file, it is found that the validateFields method returns a Promise and assembles the parameters required by the validateFieldsInternal method.

validateFields(ns, opt, cb) {
  const pending = new Promise((resolve, reject) => {
    ...
    this.validateFieldsInternal(..., params, callback);
  });
  ...
  return pending;
}

Take another look at the code of the validateFieldsInternal method. It will obtain the values of rules and data fields from the fieldsStore, and store the error information in the corresponding fieldsStore after verification.

import AsyncValidator from 'async-validator';
​
validateFieldsInternal(
  fields,
  { fieldNames, action, options = {} },
  callback,
) {
  const fieldMeta = this.fieldsStore.getFieldMeta(name);
  ...
  const validator = new AsyncValidator(allRules);
  validator.validate(allValues, options, errors => {
    if (errors && errors.length) {
      errors.forEach(e => {
        ...
        const fieldErrors = get(errorsGroup, fieldName.concat('.errors'));
        fieldErrors.push(e);
      });
    }
  });
  ...
  this.setFields(nowAllFields);
  ...
}

In general, the following steps have been taken from Form initialization to Form collection verification:
1. Through form The Create method initializes some attributes to props Form for developers to call;
2. Initialize the attributes and values of the form through getFieldDecorator to achieve the effect of two-way binding;
3. If the verification is passed, save the data to fieldsStore; If the verification fails, save the error to fieldsStore and render.

2, Own Form

Effects and codes can be found in https://stackblitz.com/edit/r... see.

2.1 getFieldDecorator

/**
 * Implement the getFieldDecorator method
 * Assign initialValue to the value of the input box during initialization
 * You can get value when the input box changes
 */
const getFieldDecorator = (key: string, options: any) => {
  // Judge whether to assign value for the first time to avoid dead cycle
  const first = Object.keys(formData).indexOf(key) === -1;
​
  if (options.rules) {
    rules[key] = [...options.rules];
  }
  if (first && options.initialValue) {
    setFormData({ ...formData, [key]: options.initialValue });
  }
​
  return (formItem) => {
    if (errObj[key]) {
      formItem = {
        ...formItem,
        props: { ...formItem.props, className: 'input err' },
      };
    }
    return (
      <div className="form-item">
        {React.cloneElement(formItem, {
          name: key,
          value: formData[key] || '',
          onChange: (e: any) => {
            // Remove the error prompt when the value of the input box changes
            setErrObj({ ...errObj, [key]: '' });
            setFormData({ ...formData, [key]: e.target.value });
          },
          onBlur: () => {
            // The current default is blue
            validateFields();
          },
        })}
        <div className="err-text">{errObj[key] || ' '}</div>
      </div>
    );
  };
};

2.2 validateFields

// Binding verification method
const validateFields = (cb?: any) => {
  let errObjTemp = {};
  Object.keys(rules).forEach((key) => {
    rules[key].forEach((rule) => {
      if (rule?.required && (!formData[key] || formData[key].trim() === '')) {
        errObjTemp = {
          ...errObjTemp,
          [key]: rule?.message || `${key}Is required!`,
        };
        setErrObj(errObjTemp);
      }
    });
  });
  cb && cb(Object.keys(errObjTemp).length ? errObjTemp : undefined, formData);
};

2.3 createForm

const createForm = (FormFunc) => (props) => {
  const [formData, setFormData] = useState({});
  const [errObj, setErrObj] = useState({});
  const rules = {};
  
  ...
  
  // Mount custom methods on props
  return FormFunc({ ...props, getFieldDecorator, validateFields });
};

Keywords: Javascript Front-end

Added by BLottman on Wed, 19 Jan 2022 06:03:33 +0200