antd源碼解析(一)Form組件解析

引言

看過antd源碼的都知道,antd實際上是在一組react-componment組件的基礎上進行了一層ui封裝,本文主要解讀antd組件Form的基礎組件react-componment/form,另外會略過development模式下的warning代碼。react

Form.create

解讀源碼首先要從本身最經常使用的或者感興趣的入手,首先form組件最主要的仍是在Form.create({options})這個裝飾器入手。找到項目下的文件createForm.js,這個文件仍是主要主要對createBaseForm.js文件進行了一層封裝,提供了一些默認配置參數,下看查看createBaseForm.js裏的createBaseForm方法,改方法主要是一個裝飾器做用,包裝一個高階React組件,在props裏注入一個值爲formPropName(默認爲form)變量,全部功能在這個變量裏完成,主要內容以下git

render() {
  const { wrappedComponentRef, ...restProps } = this.props;
  const formProps = {
    [formPropName]: this.getForm(), // 來在 formPropName默認爲form,getForm方法來自`createForm.js`
  };
  if (withRef) {
    formProps.ref = 'wrappedComponent';
  } else if (wrappedComponentRef) {
    formProps.ref = wrappedComponentRef;
  }
  const props = mapProps.call(this, {
    ...formProps,
    ...restProps,
  });
  return <WrappedComponent {...props} />;
}

在裝飾器初始化的時候,Form初始化了一個只屬於該組件實例的store,用來存放當前Form組件的一些輸入的數據,主要代碼以下:github

const fields = mapPropsToFields && mapPropsToFields(this.props);  // mapPropsToFields來自於Form.create的配置參數,用來轉化來自mobx或者redux等真正的store來源的value,以初始化該Form實例的fieldsStore
this.fieldsStore = createFieldsStore(fields || {});  // createFieldsStore來自於文件`createFieldsStore.js`文件

getFieldDecorator

柯里化函數,經過id與參數聲明的輸入,返回一個函數以輸入組件爲入參的函數,經過該函數聲明的輸入組件與表單Form雙向數據綁定。redux

...
  getFieldDecorator(name, fieldOption) {
    const props = this.getFieldProps(name, fieldOption);  // 初始化一個field
    return (fieldElem) => {
      const fieldMeta = this.fieldsStore.getFieldMeta(name);  // 獲取變化(Form的onChange)後的field數據
      const originalProps = fieldElem.props;
      fieldMeta.originalProps = originalProps;  // 輸入組件初始化時保存的Prop
      fieldMeta.ref = fieldElem.ref;
      return React.cloneElement(fieldElem, {
        ...props,
        ...this.fieldsStore.getFieldValuePropValue(fieldMeta),  // 獲取prop屬性 value
      });
    };
  }
  ...

getFieldProps

查看函數 getFieldProps,主要用來初始化輸入組件的props,將特定的函數緩存在內部,如onChange事件,另外初次保存field到store中緩存

...
  getFieldProps(name, usersFieldOption = {}) {
    if (!name) {
      throw new Error('Must call `getFieldProps` with valid name string!');
    }
    delete this.clearedFieldMetaCache[name];
    const fieldOption = {
      name,
      trigger: DEFAULT_TRIGGER,
      valuePropName: 'value',
      validate: [],
      ...usersFieldOption, // 用戶輸入,如rules,initialValue
    };

    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), // 獲取輸入組件的value,若是沒有,返回initialValue
      ref: this.getCacheBind(name, `${name}__ref`, this.saveRef),
    };
    if (fieldNameProp) { // 及value
      inputProps[fieldNameProp] = 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); // 若是設置了輸入校驗rules,綁定onChange事件`this.onCollectValidate`
    });

    // make sure that the value will be collect
    if (trigger && validateTriggers.indexOf(trigger) === -1) {
      inputProps[trigger] = this.getCacheBind(name, trigger, this.onCollect); // 若是沒有綁定rules校驗,綁定默認的onChange事件
    }
    const meta = {
      ...fieldMeta,
      ...fieldOption,
      validate: validateRules,
    };
    this.fieldsStore.setFieldMeta(name, meta);  // 保存field到store中
    if (fieldMetaProp) {
      inputProps[fieldMetaProp] = meta;
    }
    if (fieldDataProp) {
      inputProps[fieldDataProp] = this.fieldsStore.getField(name);
    }
    return inputProps;
  },
  ...

getCacheBind

getCacheBind方法,緩存函數,使用bind方法綁定上下文並緩存部分參數,返回一個新的函數,用作onChange及數據校驗。antd

...
  getCacheBind(name, action, fn) {
    if (!this.cachedBind[name]) {
      this.cachedBind[name] = {};
    }
    const cache = this.cachedBind[name];
    if (!cache[action]) {
      cache[action] = fn.bind(this, name, action); // 綁定參數並返回
    }
    return cache[action];
  },
  ...

onCollectCommon

getFieldProps方法中看到利用getCacheBind方法當無rules的時候綁定了一個onCollect方法,onCollect方法主要調用onCollectCommon方法,並將獲得的結果保存到store。app

onCollectCommon(name, action, args) {
  const fieldMeta = this.fieldsStore.getFieldMeta(name);
  if (fieldMeta[action]) {  // 若是getFieldDecorator方法中的參數定義了onChange,則觸發改onChange
    fieldMeta[action](...args);
  } else if (fieldMeta.originalProps && fieldMeta.originalProps[action]) { // 若是輸入組件綁定了onChange,則觸發該onChange
    fieldMeta.originalProps[action](...args);
  }
  const value = fieldMeta.getValueFromEvent ?  // 獲取更新後的value,兼容原生組件e.target.value
    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 });
},

onCollectValidate

在有輸入rules的時候getCacheBind方法綁定onCollectValidate做爲onChange事件,該方法作了除了調用了onCollectCommon事件之外,還調用了校驗方法validateFieldsInternalasync

validateFieldsInternal

該方法主要是從store中獲取rules校驗規則並標準化後,使用async-validator模塊進行校驗,並把結果保存到store中,本文不作講解。函數

setFields

該方法主要是設置store中的field,由於store的數據是不可觀測的數據,不會引發頁面的重渲染,該方法也負責調用forceUpdate()強制更新頁面。ui

setFields(maybeNestedFields, callback) {
  const fields = this.fieldsStore.flattenRegisteredFields(maybeNestedFields); // 處理field嵌套問題
  this.fieldsStore.setFields(fields);
  if (onFieldsChange) {  // 若是設置有FieldsChange事件監聽事件變化,則觸發事件
    const changedFields = Object.keys(fields)
      .reduce((acc, name) => set(acc, name, this.fieldsStore.getField(name)), {});
    onFieldsChange(this.props, changedFields, this.fieldsStore.getNestedAllFields());
  }
  this.forceUpdate(callback);  // 強制更新視圖
},

最後

主要方法大概就上面這些,其中流程差很少在每次setFields以前,會在store中存一個field的變化字段fieldMeta,在最後強制更新頁面的時候,將該變量取出來作處理後覆蓋到field,全部數據保存在field中,並提供了一些hock方法如setFieldsValuevalidateFields等方法設置和獲取store中的field字段和值。

相關文章
相關標籤/搜索