乐趣区

关于javascript:Form-表单在数栈的应用下深入篇

这篇文章的主题为咱们对 Form 表单在数栈产品中应用之后理解消化的一个过程,通过介绍一些 Form
表单中罕用到的办法,来了解局部设计思维,加深咱们对技术的谋求。次要介绍 Form 表单的创立和 Form 表单双向绑定(getFieldDecorator)。

后文中所提到的 Form 表单均为 Antd 3.x 中的 Form 组件,以下简称为 Form 表单。在 Form 表单在数栈的利用(上): 校验篇 中提到,咱们生在一个最好的时代,其实是他人造好轮子帮咱们做了一些事件,那咱们明天看一看,他人的轮子是怎么造的,咱们本人能不能实现。留心过 Antd 的同学可能有印象,Antd 是基于 react-component 组件进行了 UI 封装,文章会以 react-component/form 的代码为主。

一、他人的 Form

1.1 From.create

先查看 createForm.js 文件,该文件次要是对 createBaseForm.js 文件进行了一层封装,并加上一些罕用的办法。

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;

接下来查看一下 createBaseForm.js 文件,次要查看该文件中的 createBaseForm 办法,这个办法起到装璜器的作用,在 props 中包装了一个默认为 form 的变量,在这个变量中实现 Form 的所有性能。createBaseForm 的作用是拷贝以后传递来的组件,也就是调用函数将以后组件传递上来作为被包装组件,最终返回一个被包装过的具备新属性的组件。

render() {const { wrappedComponentRef, ...restProps} = this.props; // eslint-disable-line
  const formProps = {
    // getForm 办法来自 createForm.js,在 props 中包装一个 formPropName 变量,默认为 form
    [formPropName]: this.getForm(),};
  // 获取 form 的实例
  if (withRef) {formProps.ref = 'wrappedComponent';} else if (wrappedComponentRef) {formProps.ref = wrappedComponentRef;}
  const props = mapProps.call(this, {
    ...formProps,
    ...restProps,
  });
  return <WrappedComponent {...props} />;
}

装璜器(decorator):是一种与 相干的语法,次要用来批改类和类办法(类属性),大部分面向对象的编程语言都反对这种语法,比方 Java、Python。装璜器能够简略了解为:能对一些 对象 进行批改,而后返回一个被包装过的 对象

综合来看,Form.create(options) 实际上是对咱们的业务组件进行了一次封装,进行了 Form 相干属性的初始化,挂载了一些须要应用的办法,并将这些办法增加到 props.form 下。

1.2 getFieldDecorator

<FormItem {...formItemLayout} label="姓 名" >
  {getFieldDecorator('name', {
    initialValue: userInfo.name,
    rules: [{ required: true, message: '姓名不可为空!'}
    ]
  })(<Input placeholder="请输出姓名" />)}
</FormItem>

从上述应用代码和下方实现办法能够看出,getFieldDecorator 是一个柯里化的函数,通过 id 和参数的输出,返回以输出组件为入加入上新属性的一个 Dom 节点,把 option 的 valuePropName、getValueProps、initialValue、rules 等各种 props 挂载到输出组件上。

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,
      // 没有 initialValue 时为 undefined,有则是 initialValue 的值
      ...this.fieldsStore.getFieldValuePropValue(fieldMeta),
    });
    return supportRef(fieldElem) ? (decoratedFieldElem) : (<FieldElemWrapper name={name} form={this}>
        {decoratedFieldElem}
      </FieldElemWrapper>
    );
  };
}

getFieldDecorator 有以下两个作用,可在 createBaseForm.js 文件的 getFieldPropsgetFieldValuePropValue 办法中别离验证:

  • 在初始化数据字段时将数据字段放到 fieldsStore 中;
  • 挂载 props 到输出组件上时会从 fieldsStore中读取数据字段。

1.3 validateFields

通常应用 validateFields 办法对咱们的表单数据进行校验,查看 createBaseForm.js 文件中 validateFields 办法的实现后,发现 validateFields 办法返回一个 Promise 并且拼装 validateFieldsInternal 办法须要的参数。

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

再看 validateFieldsInternal 办法的代码,它会从 fieldsStore 中获取 rules 和数据 fields 的值,校验后将错误信息别离存储到对应的 fieldsStore 中。

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);
  ...
}

总得来说,Form 表单从初始化到表单收集校验通过了以下几个步骤:
1、通过 Form.create 办法初始了一些属性到 props.form 中,供开发者调用;
2、通过 getFieldDecorator 初始化表单的属性和值,达到双向绑定的成果;
3、校验通过,把数据存到 fieldsStore 中;校验不通过,把 error 存到 fieldsStore 中,渲染。

二、本人的 Form

成果和代码能够在 https://stackblitz.com/edit/r… 查看。

2.1 getFieldDecorator

/**
 * 实现 getFieldDecorator 办法
 * 初始化时将 initialValue 赋值给输入框的 value
 * 输入框变动时能够拿到 value
 */
const getFieldDecorator = (key: string, options: any) => {
  // 判断是否第一次赋值,防止死循环
  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) => {
            // 输入框值变动时去除谬误提醒
            setErrObj({...errObj, [key]: '' });
            setFormData({...formData, [key]: e.target.value });
          },
          onBlur: () => {
            // 以后默认 blur 时进行校验
            validateFields();},
        })}
        <div className="err-text">{errObj[key] || ' '}</div>
      </div>
    );
  };
};

2.2 validateFields

// 绑定校验办法
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}为必填项!`,
        };
        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 = {};
  
  ...
  
  // 将自定义办法挂载到 props 上
  return FormFunc({...props, getFieldDecorator, validateFields});
};
退出移动版