antd源码解析(一)Form组件解析

引言

看过antd源码的都知道,antd其实是在一组react-componment组件的基础上进行了一层ui封装,本文主要解读antd组件Form的基础组件react-componment/form,另外会略过development模式下的warning代码。

Form.create

解读源码首先要从自己最常用的或者感兴趣的入手,首先form组件最主要的还是在Form.create({options})这个装饰器入手。找到项目下的文件createForm.js,这个文件还是主要主要对createBaseForm.js文件进行了一层封装,提供了一些默认配置参数,下看查看createBaseForm.js里的createBaseForm方法,改方法主要是一个装饰器作用,包装一个高阶React组件,在props里注入一个值为formPropName(默认为form)变量,所有功能在这个变量里完成,主要内容如下

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组件的一些输入的数据,主要代码如下:

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双向数据绑定。

  ...
  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及数据校验。

  ...
  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。

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事件以外,还调用了校验方法validateFieldsInternal

validateFieldsInternal

该方法主要是从store中获取rules校验规则并标准化后,使用async-validator模块进行校验,并把结果保存到store中,本文不做讲解。

setFields

该方法主要是设置store中的field,因为store的数据是不可观测的数据,不会引起页面的重渲染,该方法也负责调用forceUpdate()强制更新页面。

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字段和值。


麦乐
菜鸟历程
3.8k 声望
956 粉丝
0 条评论
推荐阅读
深入理解Chrome V8垃圾回收机制
最近,项目进入维护期,基本没有什么需求,比较闲,这让我莫名的有了危机感,每天像是在混日子,感觉这像是在温水煮青蛙,已经毕业3年了,很怕自己到了5年经验的时候,能力却和3年经验的时候一样,没什么长进。于...

_杨溜溜38阅读 8.3k评论 3

ESlint + Stylelint + VSCode自动格式化代码(2023)
安装插件 ESLint,然后 File -&gt; Preference-&gt; Settings(如果装了中文插件包应该是 文件 -&gt; 选项 -&gt; 设置),搜索 eslint,点击 Edit in setting.json

谭光志34阅读 20.8k评论 9

安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城32阅读 7.3k评论 5

封面图
涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco24阅读 2.3k评论 3

在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 2k

封面图
过滤/筛选树节点
又是树,是我跟树杠上了吗?—— 不,是树的问题太多了!🔗 相关文章推荐:使用递归遍历并转换树形数据(以 TypeScript 为例)从列表生成树 (JavaScript/TypeScript) 过滤和筛选是一个意思,都是 filter。对于列表来...

边城18阅读 7.8k评论 3

封面图
Vue2 导出excel
2020-07-15更新 excel导出安装 {代码...} src文件夹下新建一个libs文件夹,新建一个excel.js {代码...} vue页面中使用 {代码...} ===========================以下为早期的文章今天在开发的过程中需要做一个Vue的...

原谅我一生不羁放歌搞文艺14阅读 20.1k评论 9

3.8k 声望
956 粉丝
宣传栏