首发于 方凳雅集

rc-form源码浅析

rc-form源码浅析

最近在react工程中用到了antd,想学习一下其中form表单的实现原理;antd的form组件是在rc-form的基础上进行封装的,因此本文主要就以下几个点针对性的看了一下rc-form的源码。

  • 在每次使用form时,为什么必须要用form.create包一层
  • form表单如何实现数据托管,getFieldDeractor干了什么
  • 如何实现双向绑定
  • 如何强制更新表单的值
  • 如何进行校验

form的使用方式

antd form的使用方式如下,针对每个表单组件使用getFieldDeractor函数包一下,getFieldDeractor看着像是一个高阶组件,传入input组件并返回新的组件,在底部使用Form.create包一下整个表单。在初期使用的时候,经常会碰到一个报错,读取不到getFieldDecorator, 检查发现是漏写了Form.create。那么问题来了,这个Form.create是干什么的?

<Form layout="inline" onSubmit={this.handleSubmit}>
        <Form.Item validateStatus={usernameError ? 'error' : ''} help={usernameError || ''}>
          {getFieldDecorator('username', {
            rules: [{ required: true, message: 'Please input your username!' }],
            <Input
              prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
              placeholder="Username"
        </Form.Item>
</Form>
const WrappedHorizontalLoginForm = Form.create({ name: 'horizontal_login' })(HorizontalLoginForm);

form.create的实现

首先从Form.create看起!感觉搞了什么事情……3.x版本的antd form使用时都要用Form.create包裹一下,不然会经常出现this.props.form不存在的情况。先来看看form.create都干了些什么

export default class Form extends React.Component<FormProps, any> {
    static create = function create<TOwnProps extends FormComponentProps>(
    options: FormCreateOption<TOwnProps> = {},
  ): FormWrappedProps<TOwnProps> {
    return createDOMForm({
      fieldNameProp: 'id',
      ...options,
      fieldMetaProp: FIELD_META_PROP,
      fieldDataProp: FIELD_DATA_PROP,
}

在Form的代码里面有一个静态方法create,在返回时调用了rc-form里面的createDomForm。这个方法把mixin里面的一些方法作为参数,传入createBaseForm方法

function createForm(options) {
    // 把mixin传进去
  return createBaseForm(options, [mixin]);
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,
};

这里的mixin是一个getForm方法,里面返回了例如getFieldDecorator、getFieldsValue等一些常见的,需要通过this.props.form得到的方法,接下来看看createBaseForm

function createBaseForm(option = {}, mixins = []) {
    return function decorate(WrappedComponent) {
    var Form = createReactClass({
      getInitialState: function getInitialState() {
        var _this = this;
        // 每个Form都有自己的fieldsStore,用来存储一些form相关的
        var fields = mapPropsToFields && mapPropsToFields(this.props);
        this.fieldsStore = createFieldsStore(fields || {});
    // (一堆api……)
    render() {
        const { wrappedComponentRef, ...restProps } = this.props; // eslint-disable-line
        // 这里是form的一些属性
        const formProps = {
            // formPropName,默认值是form
          [formPropName]: this.getForm(),
        const props = mapProps.call(this, {
          ...formProps,
          ...restProps,
        return <WrappedComponent {...props} />;  // 原有属性和form属性一起返回给组件
    return argumentContainer(unsafeLifecyclesPolyfill(Form), WrappedComponent);
}

这里通过调用createBaseForm,返回了一个高阶函数decorate,这个函数接收一个组件并返回一个组件。这里通过属性代理(Props Proxy)的方式把原有的props和新的props一起返回给组件,返回的新组件通过argumentContainer包裹,函数使用了一个库 hoist-non-react-statics, 主要是为了拷贝传入组件的一些静态方法到新组件上( react文档 有提到过)

import hoistStatics from 'hoist-non-react-statics';
export function argumentContainer(Container, WrappedComponent) {
  /* eslint no-param-reassign:0 */
  Container.displayName = 'Form(' + getDisplayName(WrappedComponent) + ')';
  Container.WrappedComponent = WrappedComponent;
  return hoistStatics(Container, WrappedComponent);
}

总结一下form.create整体流程

1、调用Form.create()来创建一个表单 2、Form初始化了一个只属于该组件实例的 fieldStore ,用来存放当前Form组件的一些输入的数据


其中fileldsStore中包含了当前form的主要信息, fieldMeta,可以看成是一个表单项的描述,即配置信息不涉及值的问题:

  • name 字段的名称
  • originalProps 被 getFieldDecorator() 装饰的组件的原始 props
  • rules 校验的规则
  • trigger 触发数据收集的时机 默认 onChange
  • validate 校验规则和触发事件
  • valuePropName 子节点的值的属性,例如 checkbox 应该设为 checked
  • getValueFromEvent 如何从 event 中获取组件的值
  • hidden 为 true 时,校验或者收集数据时会忽略这个字段

fields主要用于记录每个表单的实时属性,主要包括:

  • dirty 数据是否已经改变,但未校验
  • errors 校验文案
  • name 字段名称
  • touched 数据是否更新过
  • value 字段的值
  • validating 校验状态

这里的方法有点多,可以根据一张图来参考下


数据托管:getFieldDeractor

getFieldDeractor主要目的是“托管”,表单控件会自动添加 value(或 valuePropName 指定的其他属性)onChange(或 trigger 指定的其他属性),其内部实现如下:

getFieldDecorator(name, fieldOption) {
  // 获取表单属性,会根据传入的表单项,更新(没有则会创建)fielsStore里的FieldMeta,
  // 返回表单项属性。会包含value等属性,绑定onChange事件。
  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;
    // 返回clone元素,并且把新的属性以及onChange事件都加上
    return React.cloneElement(fieldElem, {
      ...props,
      ...this.fieldsStore.getFieldValuePropValue(fieldMeta),
}

getFieldsProps获取新组件的props

getFieldDeractor主要是通过调用getFieldsProps方法,为返回后的组件增加了一些表单属性,绑定了onChange对应的事件用于组件更新等;getFieldProps方法里面进行了设置初始值、绑定value、以及通过是否需要校验来判断onChange绑定不同的事件等操作

getFieldProps(name, usersFieldOption = {}) {
        const fieldOption = {
          name,
          trigger: DEFAULT_TRIGGER,  // DEFAUKT_TRIGGER的默认值为onChange
          valuePropName: 'value',
          validate: [],
          ...usersFieldOption,
        const {
          rules,
          trigger,
          validateTrigger = trigger,
          validate,
        } = fieldOption;
        const fieldMeta = this.fieldsStore.getFieldMeta(name);
        // 将初始值设置到fieldMeta中
        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);
        // 当需要校验时,onChange事件绑定this.onCollectValidate
        validateTriggers.forEach(action => {
          if (inputProps[action]) return;
          inputProps[action] = this.getCacheBind(
            name,
            action,
            this.onCollectValidate,
        // 不需要校验时onChange对应onCollect
        if (trigger && validateTriggers.indexOf(trigger) === -1) {
          inputProps[trigger] = this.getCacheBind(
            name,
            trigger,
            this.onCollect,
        const meta = {
          ...fieldMeta,
          ...fieldOption,
          validate: validateRules,
        // 保存到fieldStore中
        this.fieldsStore.setFieldMeta(name, meta);
        if (fieldMetaProp) {
          inputProps[fieldMetaProp] = meta;
        if (fieldDataProp) {
          inputProps[fieldDataProp] = this.fieldsStore.getField(name);
        // This field is rendered, record it
        this.renderFields[name] = true;
        return inputProps;
      },

getFieldValuePropValue获取value

可以注意到getFieldDecorator底部调用了调用了getFieldValuePropValue来设置value的值,这就引发了3.x版本的一个initialValue受控的问题。问题在于:假如一个表单设置了initialValue && 这个表单还没有任何输入 && initialValue的值受控,你就会发现表单的值随着initialValue的改变一起跟着变了,这是由于在赋值时,会先检测fieldStore中有没有这个组件的值,没有的话就会取initialValue的值。

// 这个函数式在getProps的时候调用的
function getFieldValuePropValue(fieldMeta) {
      var name = fieldMeta.name,
          getValueProps = fieldMeta.getValueProps,
          valuePropName = fieldMeta.valuePropName;
      var field = this.getField(name);
     // 注意这里,返回的是field的value,没有的话则使用initValue
      var fieldValue = 'value' in field ? field.value : fieldMeta.initialValue;
      if (getValueProps) {
        return getValueProps(fieldValue);
      return _defineProperty({}, valuePropName, fieldValue);
    }

总结一下:

  • 用户通过getFieldDecorator给表单传入一些配置项,key/初始值/校验规则等
  • getDecorator会通过调用getFieldProps来创建/更新对应的fieldStore, 生成新的props,并根据是否需要校验给onChange绑定不同的方法。最终讲这些信息传入到clone的表单组件返回

表单如何实现双向绑定

表单数据更新时onChange绑定的事件及流程如下:


正常我们在react实现双向绑定通常是通过onChange事件,form表单中通过getFieldDecorator来接管双向绑定及一系列复杂的校验工作;在前面我们提到,在进行初始化表单属性时(getFieldProps),对没有rules的表单绑定了onCollent事件,需要校验的绑定了onCollectValidate方法,他们都主要调用了 onCollectCommon 方法,并将得到的结果保存到fieldStore。

// getFieldProps中的事件绑定
const validateTriggers = getValidateTriggers(validateRules);
  validateTriggers.forEach(action => {
    if (inputProps[action]) return;
      inputProps[action] = this.getCacheBind(
      name,
      action,
      this.onCollectValidate,
if (trigger && validateTriggers.indexOf(trigger) === -1) {
  inputProps[trigger] = this.getCacheBind(
    name,
    trigger,
    this.onCollect,
        onCollect(name_, action, ...args) {
        const { name, field, fieldMeta } = this.onCollectCommon(
          name_,
          action,
          args,
        const { validate } = fieldMeta;
        this.fieldsStore.setFieldsAsDirty();
        const newField = {
          ...field,
          dirty: hasRules(validate),
        this.setFields({
          [name]: newField,
onCollectCommon(name, action, args) {
  const fieldMeta = this.fieldsStore.getFieldMeta(name);
   // 如果getFieldDecorator方法中的参数定义了onChange,则触发该onChange
  if (fieldMeta[action]) {
    fieldMeta[action](...args);
  // 如果输入组件绑定了onChange,则触发该onChange
  else if (fieldMeta.originalProps && fieldMeta.originalProps[action]) {
    fieldMeta.originalProps[action](...args);
  // 获取事件中更新后的value
  const value = fieldMeta.getValueFromEvent ? 
    fieldMeta.getValueFromEvent(...args) :
    getValueFromEvent(...args);
  if (onValuesChange && value !== this.fieldsStore.getFieldValue(name)) {  // 如果Form.create时用户定义有onValuesChange,则触发
    const valuesAll = this.fieldsStore.getAllValues();
    const valuesAllSet = {};
    valuesAll[name] = value;
    Object.keys(valuesAll).forEach(key => set(valuesAllSet, key, valuesAll[key]));
    onValuesChange(this.props, set({}, name, value), valuesAllSet);
  const field = this.fieldsStore.getField(name);    // 获取合并field,并返回
  return ({ name, field: { ...field, value, touched: true }, fieldMeta });
},

当表单数据变化是会触发onChange,调用对应的onCollect方法,在起内部通过调用onCollectCommon来执行对应的onChange,并获取事件中更新后的值,生成新的field,然后调用setFields触发更新;

如何强制更新表单的值

我们可以通过setFieldsValue来强制更新表单的值,具体实现如下:

setFieldsValue(changedValues, callback) {
  const { fieldsMeta } = this.fieldsStore;
  const values = this.fieldsStore.flattenRegisteredFields(changedValues);
  const newFields = Object.keys(values).reduce((acc, name) => {
  }, {});
  // 调用setFields更新fieldStore中的值
  this.setFields(newFields, callback);
  // 如果设置了全局onValuesChange事件,则触发
  if (onValuesChange) {
    const allValues = this.fieldsStore.getAllValues();
    onValuesChange(
      [formPropName]: this.getForm(),
      ...this.props,
      changedValues,
      allValues,
setFields(maybeNestedFields, callback) {
  const fields = this.fieldsStore.flattenRegisteredFields(
    maybeNestedFields,
  // 更新filedStore中的值
  this.fieldsStore.setFields(fields);
  // 触发强制更新
  this.forceUpdate(callback);
}

由于antd组件统一维护了一个filedStore,没有涉及到state, 所以所有涉及到数据更新的都需要先对fieldStore里面的fileds更新后,再手动触发更新;强制触发更新会重新执行getFieldDeractor,整个过程和最初创建时相同,只是此时fieldStore里对应的field已经有值了,在执行时我们拿到了这个value,在cloneElement的时候,加在了元素的属性上,最终元素进行了更新。 但是,每次通过setFields触发forceUpdate会有性能问题,每次涉及到表单内的值改变,都会整个渲染一遍,性能大大降低,毕竟全部重新渲染了一遍……

如何进行校验

前面提到,如果配置了validateTrigger的组件,会绑定onCollectValidate事件,实现如下:

onCollectValidate(name_, action, ...args) {
    // 从onCollectCommon中获取更新后的值
  const { field, fieldMeta } = this.onCollectCommon(name_, action, args);
  // 获取组件最新的值
  const newField = {
    ...field,
    dirty: true,
  this.fieldsStore.setFieldsAsDirty();
  // 调用下面方法执行校验
  this.validateFieldsInternal([newField], {
    action,
    options: {
      firstFields: !!fieldMeta.validateFirst,
validateFieldsInternal(
  fields,
  { fieldNames, action, options = {} },
  callback,
   获取校验规则等
  // 主要使用async-validator来执行具体的校验工作
  const validator = new AsyncValidator(allRules);
  validator.validate(allValues, options, errors => {
    const errorsGroup = {
      ...alreadyErrors,
    if (errors && errors.length) {
      errors.forEach(e => {
        // 省略...
        const fieldErrors = get(errorsGroup, fieldName.concat('.errors'));
        fieldErrors.push(e);
    const expired = [];
    const nowAllFields = {};
    Object.keys(allRules).forEach(name => {
      const fieldErrors = get(errorsGroup, name);
      const nowField = this.fieldsStore.getField(name);
      // avoid concurrency problems
      if (!eq(nowField.value, allValues[name])) {
        expired.push({
          name,
      } else {
        nowField.errors = fieldErrors && fieldErrors.errors;
        nowField.value = allValues[name];
        nowField.validating = false;
        nowField.dirty = false;
        nowAllFields[name] = nowField;