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

rc-form原理浅析 #8

Open
DrugsZ opened this issue Jul 29, 2019 · 0 comments
Open

rc-form原理浅析 #8

DrugsZ opened this issue Jul 29, 2019 · 0 comments
Labels
Antd be related to Antd rc系列 React be related to React

Comments

@DrugsZ
Copy link
Owner

DrugsZ commented Jul 29, 2019

Form组件

Antd提供了Form组件组件来进行复杂表单的处理,

使用Form.create处理后的表单具有自动收集数据并校验的功能,但如果您不需要这个功能,或者默认的行为无法满足业务需求,可以选择不使用 Form.create 并自行处理数据。

那我们肯定需要,因为其灵魂就在于其,那么我们抛开布局,单纯来看一下,Form.create是如何实现

哪里是核心?

form.create调用rc-formcreateDOMForm,而createDOMForm则直接调用了createBaseForm,所以不严谨来说的话,ant-designform.create我们可以认为只是createBaseForm别名,所以我们来看看createBaseForm是如何实现的?

React-form实现的双向绑定与Vue

​ 经过 Form.create 包装的组件将会自带 this.props.form 属性,

​ 而form属性中提供的getFieldDecorator则是实现数据自动收集的核心;

​ 说到这个话题,我们跑一下题,目前国内前端呈现ReactVue双足鼎立的态势,ng的使用量是远远低于这两大框架的,可能有人会说了,不是说Form组件吗?又要开始写娱乐圈了?并不是,

​ 我们来思考一下,简单了解过Vue的都能知道Vue的核心卖点是数据的双向绑定,而React则多为单向数据流,写两个简单的例子

let data = 0

//Vue
<input v-model = "data"/>
    
//React
<input value={data} onChange={(e) => data = e.target.value}/>

​ 假设data在这两个例子中都是被观测的,那么VueReact的例子都实现了对data的双向绑定,也就是说,Vue总的来说也是单向数据流,在这两个例子中不同的是绑定方式,不同的是Vue提供了v-model语法糖,而React则给我们提供了更细粒度处理事件的机会,但是Vue也可以写成这种方式,只不过是v-model代劳了而已,那么到这里,我们回头再看看getFieldDecorator是不是跟v-model越看越像呢,

getFieldDecorator('name',{})(
    <input/>
)
<input v-model = "data"/>

那么是不是Form是对React在表单上类似于Vue的另类实现呢?

是,也不是

是与不是

​ 为什么说是呢,因为getFieldDecorator默认绑定了valueonChange事件,这和Vue是相同的思想,那么不是又在哪里呢,我们回想一下,通过Vue的创建表单跟我们使用antd最大区别在哪里? 在于表单项,诚然Vue从底层API为我们实现了数据与UI的双向绑定,但是我们还是要自己去管理数据的,当然现在的现代框架提出的思想就是用数据去驱动UI,但是当我们使用Form时我们没有必要去管理它的数据,我们只需要在我们要提交数据时获取到Form中的值就可以了,这种更符合组件的思想,局部的数据,存储在局部的组件中,如果用这种思想来看Vue的Form的话,就会觉得Form的数据被提升到了父组件来进行管理.这对我们来说是不必要的

当然,这是我的个人看法,如果Vue的相关组件库有类似实现,请各位不吝指正,同时此观点只是个人看法,希望各位不要认为我的思想就是对的

Form.create的具体实现

​ 那我们说了这么多看一下其具体实现,因为我们在使用Form组件是使用最多的就是getFieldDecorator方法,所以我们首先来看一下他的实现原理

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;
          return React.cloneElement(fieldElem, {
            ...props,
            ...this.fieldsStore.getFieldValuePropValue(fieldMeta),
          });
        };
  },

看一下这个函数的构成,调用getFieldProps方法构建props,随后将props跟其他相关配置挂载到传入的ReactNode上,所以从此来看,主要的逻辑配置在getFieldProps方法上,我 们来看一下getFieldProps的实现

 getFieldProps(name, usersFieldOption = {}) {

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

        // This field is rendered, record it
        this.renderFields[name] = true;

        return inputProps;
      },

我删除了部分主要逻辑无关代码,我们看一下整个函数的思路,该函数首先进行了默认值的配置,如果未配置triggervaluePropName则使用默认值,随后调用fieldsStore.getFieldMeta,fieldsStore在整个form中尤为关键,其作用是作为一个数据中心,让我们免除了手动去维护form中绑定的各个值,同时也是我刚才说的局部的数据存储于局部的组件思想.那么我们看一下fieldsStore.getFieldMeta做了什么

//getFieldMeta在src/createFieldsStore下
  getFieldMeta(name) {
    this.fieldsMeta[name] = this.fieldsMeta[name] || {};
    return this.fieldsMeta[name];
  }

它的作用和它的名字一样是根据name获取FieldMeta,如果没有则创建,所以我们想象一下,整个form则会根据每个fieldname值去创建索引表,现在我们知道在初始化情况下它返回的为空对象,

继续往下则是获取initialValue,关于这个可以看一下antd form的文档继续往后,下面到了最重要的inputProps构建环节,首先调用getFieldValuePropValue去获取field初始值,随后创建ref函数,我们暂时略过,我们来看一下最重要的数据收集

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

我们着重来看一下这一部分代码,根据名称我们来看,validateRules应该是所有的校验规则,validateTriggers则是所有的校验规则触发事件的集合,我们来看一下这两个函数

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

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

我们看一下normalizeValidateRules函数,其会将validate``rules组合,返回一个数组,其内部的元素为一个个规则对象,并且每个元素都存在一个可以为空的trigger数组,并且将validateTrigger作为ruletriggers推入validateRules中,我们回回头看一下validateTrigger,

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

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

看一下这两个赋值,取值的语句,当我们没有配置trigger时使用DEFAULT_TRIGGER作为收集值的触发事件也就是onChange而当我们没有设置validateTrigger的时候使用trigger,这样说可能有点绕,简单点说,当我们配置了validateTrigger也就是验证触发函数时使用用户配置,未配置则使用用户配置的trigger,如果trigger用户都没有配置则全部使用默认配置也就是onChange,回过头来继续看着两个函数,getValidateTriggers则是将所有触发事件统一收集至一个数组,随后将所有validateTriggers中的事件都绑定上同一个处理函数,也就是接来下要说

 validateTriggers.forEach((action) => {
          if (inputProps[action]) return;
          inputProps[action] = this.getCacheBind(name, action, this.onCollectValidate);
        });

我们看到,不管validateTriggers中哪一种事件被触发都会通过this.getCacheBind(name, action, this.onCollectValidate);来进行处理,首先来看一下getCacheBind

getCacheBind(name, action, fn) {
        if (!this.cachedBind[name]) {
          this.cachedBind[name] = {};
        }
        const cache = this.cachedBind[name];
        if (!cache[action] || cache[action].oriFn !== fn) {
          cache[action] = {
            fn: fn.bind(this, name, action),
            oriFn: fn,
          };
        }
        return cache[action].fn;
      },

我们可以看到getCacheBind只是做了一下bind,真正的处理函数则是 this.onCollectValidate,那我们来看一下 this.onCollectValidate做了什么?

onCollectValidate(name_, action, ...args) {
    const { field, fieldMeta } = this.onCollectCommon(name_, action, args);
    const newField = {
        ...field,
        dirty: true,
    };

    this.fieldsStore.setFieldsAsDirty();

    this.validateFieldsInternal([newField], {
        action,
        options: {
            firstFields: !!fieldMeta.validateFirst,
        },
    });
},

onCollectValidate被调用,也就是数据校验函数被触发时主要做了四件事情,我们一条一条的来看

onCollectCommon(name, action, args) {
    const fieldMeta = this.fieldsStore.getFieldMeta(name);
    if (fieldMeta[action]) {
        fieldMeta[action](...args);
    } else if (fieldMeta.originalProps && fieldMeta.originalProps[action]) {
        fieldMeta.originalProps[action](...args);
    }
    const value = fieldMeta.getValueFromEvent ?
          fieldMeta.getValueFromEvent(...args) :
    getValueFromEvent(...args);
    if (onValuesChange && value !== this.fieldsStore.getFieldValue(name)) {
        const valuesAll = this.fieldsStore.getAllValues();
        const valuesAllSet = {};
        valuesAll[name] = value;
        Object.keys(valuesAll).forEach(key => set(valuesAllSet, key, valuesAll[key]));
        onValuesChange({
            [formPropName]: this.getForm(),
            ...this.props
        }, set({}, name, value), valuesAllSet);
    }
    const field = this.fieldsStore.getField(name);
    return ({ name, field: { ...field, value, touched: true }, fieldMeta });
},

我们可以看出onCollectCommon主要是获取了包装元素新的值,随后将其包装在对象中返回,返回后将其组装为一个新的名为newField的对象,执行fieldsStore.setFieldsAsDirty,而fieldsStore.setFieldsAsDirty则是标记校验状态,我们暂且略过,随后执行validateFieldsInternal我们看一下validateFieldsInternal

validateFieldsInternal(fields, {
        fieldNames,
        action,
        options = {},
      }, callback) {
        const allFields = {};
        fields.forEach((field) => {
          const name = field.name;
          if (options.force !== true && field.dirty === false) {
            if (field.errors) {
              set(alreadyErrors, name, { errors: field.errors });
            }
            return;
          }
          const fieldMeta = this.fieldsStore.getFieldMeta(name);
          const newField = {
            ...field,
          };
          newField.errors = undefined;
          newField.validating = true;
          newField.dirty = true;
          allRules[name] = this.getRules(fieldMeta, action);
          allValues[name] = newField.value;
          allFields[name] = newField;
        });
        this.setFields(allFields);
        // in case normalize
       ...dosometing
      },

因为validateFieldsInternal主要篇幅为调用AsyncValidator进行异步校验,我们暂时略过只看数据收集部分,

我们看到起最后调用了this.setFields(allFields);并传入了新的值,

看一下setFields

setFields(maybeNestedFields, callback) {
        const fields = this.fieldsStore.flattenRegisteredFields(maybeNestedFields);
        this.fieldsStore.setFields(fields);
        if (onFieldsChange) {
          const changedFields = Object.keys(fields)
            .reduce((acc, name) => set(acc, name, this.fieldsStore.getField(name)), {});
          onFieldsChange({
            [formPropName]: this.getForm(),
            ...this.props
          }, changedFields, this.fieldsStore.getNestedAllFields());
        }
        this.forceUpdate(callback);
      },

我们可以看到,setFields首先对传入的只进行与初始化相似的验证,随后,将值存入fieldsStore,调用传入的onFieldsChange,之后调用React.forceUpdate更新视图.至此,我们简单的描述了整个流程,我们简述起具体流程则类似于Vue的V-model

获取初始值=>存储值数据中心也就是fieldsStore=>绑定收集值时机函数=>触发函数=>更新最新值至数据中心=>随后调用forceUpdate强制刷新视图.

@DrugsZ DrugsZ added React be related to React Antd be related to Antd rc系列 labels Jul 29, 2019
@DrugsZ DrugsZ changed the title React-Form梳理 React-Form原理浅析 Jul 30, 2019
@DrugsZ DrugsZ changed the title React-Form原理浅析 rc-form原理浅析 Jul 30, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Antd be related to Antd rc系列 React be related to React
Projects
None yet
Development

No branches or pull requests

1 participant