Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
sl1673495 opened this issue Oct 5, 2018 · 0 comments
Open

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

sl1673495 opened this issue Oct 5, 2018 · 0 comments
Labels

Comments

@sl1673495
Copy link
Owner

sl1673495 commented Oct 5, 2018

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
}
这样的结果给外部做表单提交。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant