ssh的博客

react-component源码学习(1) rc-form

October 5, 2018 • ☕️☕️ 8 min read

rc-form作为ant-design系列实现表单组件的底层组件, 通用性和强大的功能兼得,这得益于它底层的精妙实现,rc-form是典型的高阶组件(higher-order component)

下面从一个官方的简单示例说起。

import { createForm, formShape } from 'rc-form';

class Form extends React.Component {
  static propTypes = {
    form: formShape,
  };

  submit = () => {
    this.props.form.validateFields((error, value) => {
      console.log(error, value);
    });
  }

  render() {
    let errors;
    const { getFieldProps, getFieldError } = this.props.form;
    return (
      <div>
        <input {...getFieldProps('normal')}/>
        <input {...getFieldProps('required', {
          onChange(){}, // have to write original onChange here if you need
          rules: [{required: true}],
        })}/>
        {(errors = getFieldError('required')) ? errors.join(',') : null}
        <button onClick={this.submit}>submit</button>
      </div>
    );
  }
}

export createForm()(Form);

可以看到在最后用createForm这个函数执行返回的函数包裹了Form组件, 正因为如此在render中才可以从props里拿到from, 这是rc-form提供给我们的,接下来看看这个form是如何注入进去的。

createForm.js

import createBaseForm from './createBaseForm';

export const mixin = {
  getForm() {
    return {
      getFieldsValue: this.fieldsStore.getFieldsValue,
      getFieldValue: this.fieldsStore.getFieldValue,
      getFieldInstance: this.getFieldInstance,
      setFieldsValue: this.setFieldsValue,
      setFields: this.setFields,
      setFieldsInitialValue: this.fieldsStore.setFieldsInitialValue,
      getFieldDecorator: this.getFieldDecorator,
      getFieldProps: this.getFieldProps,
      getFieldsError: this.fieldsStore.getFieldsError,
      getFieldError: this.fieldsStore.getFieldError,
      isFieldValidating: this.fieldsStore.isFieldValidating,
      isFieldsValidating: this.fieldsStore.isFieldsValidating,
      isFieldsTouched: this.fieldsStore.isFieldsTouched,
      isFieldTouched: this.fieldsStore.isFieldTouched,
      isSubmitting: this.isSubmitting,
      submit: this.submit,
      validateFields: this.validateFields,
      resetFields: this.resetFields,
    };
  },
};

function createForm(options) {
  return createBaseForm(options, [mixin]);
}

export default createForm;

这是我们在render中调用的createForm 可以看到mixin中的getForm里的属性和我们使用的很相似,其实这就是最终注入的props.form属性, 对外暴露的createForm方法最终调用了createBaseForm并将mixin传入。

createBaseForm.js

function createBaseForm(option = {}, mixins = []) {
  const {
    validateMessages,
    onFieldsChange,
    onValuesChange,
    mapProps = identity,
    mapPropsToFields,
    fieldNameProp,
    fieldMetaProp,
    fieldDataProp,
    formPropName = 'form',
    name: formName,
    // @deprecated
    withRef,
  } = option;

  return function decorate(WrappedComponent) {
    const Form = createReactClass({
      mixins,
      .......,
      render() {
        const { wrappedComponentRef, ...restProps } = this.props;
        const formProps = {
          [formPropName]: this.getForm(),
        };
        if (withRef) {
          if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
            warning(
              false,
              '`withRef` is deprecated, please use `wrappedComponentRef` instead. ' +
                'See: https://github.com/react-component/form#note-use-wrappedcomponentref-instead-of-withref-after-rc-form140'
            );
          }
          formProps.ref = 'wrappedComponent';
        } else if (wrappedComponentRef) {
          formProps.ref = wrappedComponentRef;
        }
        const props = mapProps.call(this, {
          ...formProps,
          ...restProps,
        });
        return <WrappedComponent {...props}/>;
      },
    });

    return argumentContainer(Form, WrappedComponent);
  };
}

可以看出createBaseForm是一个典型的高阶函数,接受options和mixin作为参数,返回一个装饰器decorate函数, 这个decorate函数接受一个react component作为参数,所以我们在外部调用可以使用

createForm()(Form);

这样去获得一个注入了props的组件, 接下来看render中的实现

        const formProps = {
          [formPropName]: this.getForm(),
        };
        return <WrappedComponent {...props}/>;

formPropName在defaultProps中默认被设置为’form’, getForm是从mixin中注入的, 其实就相当于注入了

{
  form: {
      getFieldsValue: this.fieldsStore.getFieldsValue,
      getFieldValue: this.fieldsStore.getFieldValue,
      getFieldInstance: this.getFieldInstance,
      setFieldsValue: this.setFieldsValue,
      setFields: this.setFields,
      setFieldsInitialValue: this.fieldsStore.setFieldsInitialValue,
      getFieldDecorator: this.getFieldDecorator,
      getFieldProps: this.getFieldProps,
      getFieldsError: this.fieldsStore.getFieldsError,
      getFieldError: this.fieldsStore.getFieldError,
      isFieldValidating: this.fieldsStore.isFieldValidating,
      isFieldsValidating: this.fieldsStore.isFieldsValidating,
      isFieldsTouched: this.fieldsStore.isFieldsTouched,
      isFieldTouched: this.fieldsStore.isFieldTouched,
      isSubmitting: this.isSubmitting,
      submit: this.submit,
      validateFields: this.validateFields,
      resetFields: this.resetFields,
  }
}

看源码先从主流程看起, 知道了form是如何注入以后,我们就从示例入手, 先看看

<input {...getFieldProps('normal')}/>

中的getFieldProps是如何实现。

getFieldProps

getFieldProps(name, usersFieldOption = {}) {
        if (!name) {
          throw new Error('Must call `getFieldProps` with valid name string!');
        }
        if (process.env.NODE_ENV !== 'production') {
          warning(
            this.fieldsStore.isValidNestedFieldName(name),
            'One field name cannot be part of another, e.g. `a` and `a.b`.'
          );
          warning(
            !('exclusive' in usersFieldOption),
            '`option.exclusive` of `getFieldProps`|`getFieldDecorator` had been remove.'
          );
        }

        delete this.clearedFieldMetaCache[name];

        const fieldOption = {
          name,
          trigger: DEFAULT_TRIGGER,
          valuePropName: 'value',
          validate: [],
          ...usersFieldOption,
        };

        const {
          rules,
          trigger,
          validateTrigger = trigger,
          validate,
        } = fieldOption;

        const fieldMeta = this.fieldsStore.getFieldMeta(name);
        if ('initialValue' in fieldOption) {
          fieldMeta.initialValue = fieldOption.initialValue;
        }

        const inputProps = {
          ...this.fieldsStore.getFieldValuePropValue(fieldOption),
          ref: this.getCacheBind(name, `${name}__ref`, this.saveRef),
        };
        if (fieldNameProp) {
          inputProps[fieldNameProp] = formName ? `${formName}_${name}` : name;
        }

        const validateRules = normalizeValidateRules(validate, rules, validateTrigger);
        const validateTriggers = getValidateTriggers(validateRules);
        validateTriggers.forEach((action) => {
          if (inputProps[action]) return;
          inputProps[action] = this.getCacheBind(name, action, this.onCollectValidate);
        });

        // make sure that the value will be collect
        if (trigger && validateTriggers.indexOf(trigger) === -1) {
          inputProps[trigger] = this.getCacheBind(name, trigger, this.onCollect);
        }

        const meta = {
          ...fieldMeta,
          ...fieldOption,
          validate: validateRules,
        };
        this.fieldsStore.setFieldMeta(name, meta);
        if (fieldMetaProp) {
          inputProps[fieldMetaProp] = meta;
        }

        if (fieldDataProp) {
          inputProps[fieldDataProp] = this.fieldsStore.getField(name);
        }

        return inputProps;
      },

这个函数接受name,和usersFieldOption两个参数

const fieldOption = {
          name,
          trigger: DEFAULT_TRIGGER, // onChange
          valuePropName: 'value',
          validate: [],
          ...usersFieldOption,
};

 const fieldMeta = this.fieldsStore.getFieldMeta(name);
 if ('initialValue' in fieldOption) {
    fieldMeta.initialValue = fieldOption.initialValue;
 }

先是一波简单的合并配置, 将usersFieldOption混入fiedOption中, 然后从this.fieldsStore中根据name提取出fieldMeta, 将initialValue填入。 fieldsStore是一个存储类,form组件内部有大量的数据需要存储和读取,所以实现了一个fieldsStore类去处理数据的流转。

class FieldsStore {
  constructor(fields) {
    this.fields = this.flattenFields(fields);
    this.fieldsMeta = {};
  }
  ...
  getFieldMeta(name) {
    this.fieldsMeta[name] = this.fieldsMeta[name] || {};
    return this.fieldsMeta[name];
  }
}

因为初始化的this.fieldsStore应该是空的, 所以这里也只是读取到了一个空对象,继续往下走。

const inputProps = {
   ...this.fieldsStore.getFieldValuePropValue(fieldOption),
   ref: this.getCacheBind(name, `${name}__ref`, this.saveRef),
};

inputProps中先是通过fieldsStore实例的getFieldValuePropValue方法传入fieldOption拿到一些属性, 在初始化的时候其实就是{ value: undefined }

  getFieldValuePropValue(fieldMeta) {
    // 对应示例中 name: 'normal', valuePropName: 'value'
    const { name, getValueProps, valuePropName } = fieldMeta;
   // 得到 {  name: 'normal'  }, 初始化的时候fields还为空
    const field = this.getField(name);
   // field中没有value, 所以去取initialValue, 示例中未传入,同样为空 
    const fieldValue = 'value' in field ?
      field.value : fieldMeta.initialValue;
    if (getValueProps) {
      return getValueProps(fieldValue);
    }
   // 初始化的时候就返回 { value: undefined }
    return { [valuePropName]: fieldValue };
  }

  getField(name) {
    return {
      ...this.fields[name],
      name,
    };
  }

ref则是通过this.cacheBind的缓存方法去取缓存了的表单元素ref 此时inputProps = { value: undefined, ref: component, }

接下来是处理有关表单验证的逻辑,

const validateRules = normalizeValidateRules(validate, rules, validateTrigger);
const validateTriggers = getValidateTriggers(validateRules);
validateTriggers.forEach((action) => {
   if (inputProps[action]) return;
   inputProps[action] = this.getCacheBind(name, action, this.onCollectValidate);
});

normalizeValidateRules方法接受的validate在示例未传入,是空数组,rules是 [{required: true}], validateTrigger是默认的onChange, 看normalizeValidateRules的实现:

export function normalizeValidateRules(validate, rules, validateTrigger) {
  const validateRules = validate.map((item) => {
    const newItem = {
      ...item,
      trigger: item.trigger || [],
    };
    if (typeof newItem.trigger === 'string') {
      newItem.trigger = [newItem.trigger];
    }
    return newItem;
  });
  if (rules) {
    validateRules.push({
      trigger: validateTrigger ? [].concat(validateTrigger) : [],
      rules,
    });
  }
  return validateRules;
}

我们发现其实返回了

validateRules: [{
  trigger: ['onChange'],
  rules: [{required: true}]
}]

在看getValidateTriggers 将上面的数组传入

export function getValidateTriggers(validateRules) {
  return validateRules
    .filter(item => !!item.rules && item.rules.length)
    .map(item => item.trigger)
    .reduce((pre, curr) => pre.concat(curr), []);
}

其实就是简单的把rules为空的项过滤掉, 因为每个rule的trigger可能有多个 所以reduce的目的是拉平成一维数组, 最后返回[‘onChange’]这样的数组

  validateTriggers = ['onChange']

最后对validateTriggers进行循环,循环体内

if (inputProps[action]) return;
inputProps[action] = this.getCacheBind(name, action, this.onCollectValidate);

其实就是把onChange: onCollectValidate 这样的校验触发逻辑混入inputProps,关于表单校验的逻辑其实是用了heyiming大大写的async-validator这个库,使用非常广泛,有空的话也可以深入研究一下,可以另开一篇了~

接下来就是合并新的meta对象,并且存入fieldsStore中对应的name存储空间。

const meta = {
   ...fieldMeta, // 初始化不存在
   ...fieldOption, // 外部传入和内部默认合并后的options
   validate: validateRules, // 上文已经给出示例中结果
};
this.fieldsStore.setFieldMeta(name, meta);

setFieldMeta实现就是一个简单的赋值,这样fieldStore内部name这个key就可以读取到存储的数据了。

 setFieldMeta(name, meta) {
    this.fieldsMeta[name] = meta;
  }

最后返回inputProps对象 混入input组件,

 return inputProps;

//大概的格式是 
{
   name: 'required',
   onChange(){}, 
   rules: [{required: true}],
}

示例中的onSubmit函数调用了validateFields, 抛开表单校验的逻辑不看,可以看到这个方法内部这句。

if (callback) {
   callback(null, this.fieldsStore.getFieldsValue(fieldNames));
}

最终整合成{ key: value } 这样的结果给外部做表单提交。