当前位置: 首页 > news >正文

rc-form之最单纯情况

前言

第一次探索这个框架,对于里面很多逻辑是不懂的,所以只能一点一点去揣摩,其中做了什么。
而学习过程中,总是禁不住好奇这里的逻辑是干什么的,那里的逻辑是什么的,在不理解这段逻辑是做什么的情况下,死磕很容易事倍功半。所以本次先从一个比较简单的场景入手,看看它的源码中做了什么手脚,至于有些逻辑没有涉及到的,先不去管它就好了。

探究内容

效果

首先上图,看看这次案例的效果。

clipboard.png

其实是上一篇案例的精简版,去掉了非空验证。但是分析的更细致些。

业务代码

import React from 'react';
import { createForm, formShape } from 'rc-form';

class Form extends React.Component {
  static propTypes = {
    form: formShape,
  };

  componentWillMount() {
    this.nameDecorator = this.props.form.getFieldDecorator('name');
  }

  onSubmit = (e) => {
    e.preventDefault();
    this.props.form.validateFields((error, values) => {
      if (!error) {
        console.log('ok', values);
      } else {
        console.log('error', error, values);
      }
    });
  };

  onChange = (e) => {
    console.log(e.target.value);
  }

  render() {
    const { getFieldError } = this.props.form;

    return (
      <form onSubmit={this.onSubmit} style={{padding: '200px'}}>
        {this.nameDecorator(
          <input
            onChange={this.onChange}
          />
        )}
        <div style={{ color: 'red' }}>
          {(getFieldError('name') || []).join(', ')}
        </div>
        <button>Submit</button>
      </form>
    );
  }
}

const WrappedForm = createForm()(Form);
export default WrappedForm;

源码分析

PS: 源码分析以代码+备注的形式展示

WrappedForm

概述

这个页面直接渲染了WrappedForm,所以我们不妨直接从WrappedForm看起。
其中WrappedForm是由rc-form提供的createForm创建的,第一个配置对象未传递,第二个参数是要修饰的组件。这里传递给了我们的业务组件

源码

createForm.js

import createBaseForm from './createBaseForm';

// 一系列给其他组件用的自定义混入
export const mixin = {
  getForm() {
    // 这里需要注意的是this是动态的,所以这里的this.xxx会根据环境改变而改变
    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) {
  // 这里调用了createBaseForm并将混入传入到该函数
  return createBaseForm(options, [mixin]);
}

export default createForm;

这里我们看到,其实createForm本身没做什么事情,只是在该文件内定义了混入的格式。
接下来我们的重点是createBaseForm中做了什么。这里只列出跟我们案例相关的内容

createBaseForm.js

//整体结构
function createBaseForm(options={}, mixins={} ) {
    const {
        mapPropsToFields,
        onFieldsChange,
        // ****其他的optinos里面的值,下文可能会用到
    } = option;
    //此处的WrappedComponent就是我们示例中,被包裹的Form组件
    return funciton decorate(WrappedComponent) {
        const Form = createReactClass({
            // 该混入包含一个getForm方法用来得到一些通用方法
            mixins,
            getInitialState() {/*mark-init,初始化组件state*/}
            componentWillReceiveProps(nextProps) {/*mark-recProps,初始化部分数据*/}
            onCollect(){/*mark-collect收集表单数据*/}
            onCollectCommon() {}
            getCacheBind() {/*mark-bind组件事件绑定等收集*/}
            getFieldDecorator() {/*mark-deco装饰组件,促进双向绑定的修饰器*/}
            getFieldProps() {/*mark-props设置字段元数据,计算被修饰组件的属性*/}
            // 一些其他函数
            
            render() {
                const { wrappedComponentRef, ...restProps } = this.props;
                const formProps = {
                  [formPropName]: this.getForm(),
                };
                // ** 精简本次分析无关的代码
                // 其中mapProps函数就是一个function(obj) {return obj};
                // 这里用了一个小技巧,就是call(this,xxx),直接将该组件上的核心方法,全都放到了子组件的属性上,而且由于该组件是createReactClass创建的,所以子组件(本例中的Form)调用这些从父组件获取的方法时,方法内部的this,指向当前组件。
                const props = mapProps.call(this, {
                  ...formProps,
                  ...restProps,
                });
                return <WrappedComponent {...props}/>;
              },
          },
        })
        //简化静态方法转移部分
        return Form;
    }
}

当createBaseForm函数在渲染函数中返回了我们的Form组件后,就可以看到Form组件中做的事情。

Form.js

class Form extends React.Component {
  static propTypes = {
    form: formShape,
  };

  componentWillMount() {
    // 上个文件,createBaseForm的装饰器函数decorate,生成的组件中渲染了该组件,
    // 并将getFieldDecorator方法通过属性传递给了它。
    this.nameDecorator = this.props.form.getFieldDecorator('name');
  }

  onSubmit = (e) => {
    e.preventDefault();
    this.props.form.validateFields((error, values) => {
      if (!error) {
        console.log('ok', values);
      } else {
        console.log('error', error, values);
      }
    });
  };

  onChange = (e) => {
    console.log(e.target.value);
  }

  render() {
    const { getFieldError } = this.props.form;

    return (
      <form onSubmit={this.onSubmit} style={{padding: '200px'}}>
        {this.nameDecorator(
          <input
            onChange={this.onChange}
          />
        )}
        {/*这里只是展示错误信息,不是我们关注点*/}
        <div style={{ color: 'red' }}>
          {(getFieldError('name') || []).join(', ')}
        </div>
        <button>Submit</button>
      </form>
    );
  }
}

重点了。关键是看看getFieldDecorator中做了什么。

 getFieldDecorator(name, fieldOption) {
    // 获取需要传递给被修饰元素的属性。包括onChange,value等
    // 同时在该props中设定用于收集元素值得监听事件(onChange),以便后续做双向数据。
    const props = this.getFieldProps(name, fieldOption);
    // 通过该函数传入(input/被修饰)元素。
    return (fieldElem) => {
      // 此处fieldStore存储字段数据信息以及元数据信息。
      // 数据信息包括value,errors,dirty等
      // 元数据信息包括initValue,defaultValue,校验规则等。
      const fieldMeta = this.fieldsStore.getFieldMeta(name);
      // 获取input上本身绑定的属性,例如该例子中的onChange打印内容函数
      const originalProps = fieldElem.props;
      fieldMeta.originalProps = originalProps;
      fieldMeta.ref = fieldElem.ref;
      return React.cloneElement(fieldElem, {
        ...props,
        ...this.fieldsStore.getFieldValuePropValue(fieldMeta),
      });
    };
  },

其中getFieldProps值得我们关注

// 简化后的内容如下
getFieldProps(name, usersFieldOption = {}) {
    // name为我们为该文本框起的name
    // 这里的数据fieldOption用来初始后面的FieldMeta。
    const fieldOption = {
      name,
      trigger: 'onChange',
      valuePropName: 'value', // checkBox取值时通过checked属性。
      ...usersFieldOption,
    };

    const {
      trigger,
      validateTrigger = trigger,
    } = fieldOption;
    
    // 第一次get元数据一般得不到,内部会返回个空对象
    const fieldMeta = this.fieldsStore.getFieldMeta(name);
    if ('initialValue' in fieldOption) {
      fieldMeta.initialValue = fieldOption.initialValue;
    }
    
    // 这里的inputProps简化后结果为{value: xxx},第一次为空。
    const inputProps = {
      ...this.fieldsStore.getFieldValuePropValue(fieldOption),
    };
    
    // make sure that the value will be collect
    // 这里用来在getFieldDecorator第二个参数为空时,确保给input绑定一个基本的onChange事件来收集input的修改值,最终放入fieldsStore中
    if (trigger && validateTriggers.indexOf(trigger) === -1) {
      inputProps[trigger] = this.getCacheBind(name, trigger, this.onCollect);
    }
    // 这里就是我们fieldsStore中设置的元数据
    const meta = {
      ...fieldMeta,
      ...fieldOption,
    };
    this.fieldsStore.setFieldMeta(name, meta);
    // 这里返回的{value: 'xxx', onChange: fn};
    return inputProps;
  },

上述代码中onChange绑定了一个onCollect,其中getCacheBind函数主要是修正函数使用时候this指针。此处忽略直接分析onCollect

  onCollect(name_, action, ...args) {
    // 通过onCollectCommon在input的onChange中触发,收集到该元素相关东西
    const { name, field, fieldMeta } = this.onCollectCommon(name_, action, args);
    const { validate } = fieldMeta;
    const newField = {
      ...field,
      dirty: hasRules(validate),//根据规则验证,此处可忽略
    };
    // 更新fieldStore中的值,主要触发了一个forceUpdate方法,重新渲染该组件
    this.setFields({
      [name]: newField,
    });
  },

最后关键的一步就是看看onCollectCommon做了什么

onCollectCommon(name, action, args) {
        const fieldMeta = this.fieldsStore.getFieldMeta(name);
        if (fieldMeta[action]) {
          fieldMeta[action](...args);
        } else if (fieldMeta.originalProps && fieldMeta.originalProps[action]) {
          // 此处调用input原来的onChange事件,即打印输入值,这个originalProps是在getFieldDecorator函数中存下的,这里只不过拿来用了。
          fieldMeta.originalProps[action](...args);
        }
        // 此处的getValueFromEvent其实就是取e.target.value.
        const value = fieldMeta.getValueFromEvent ?
          fieldMeta.getValueFromEvent(...args) :
          getValueFromEvent(...args);

        const field = this.fieldsStore.getField(name);
        return ({ name, field: { ...field, value, touched: true }, fieldMeta });
      },

至此整个数据流程基本跑通,onChange触发onCollect去改变fieldStore中的值并forceUpdate更新界面,onCollectCommon则展示了onCollect取值的细节。forceUpdate更新组件后,触发Formrender方法,又开始了之前getFieldDecorator 中读取fieldStore中值,返回被修改后的组件的流程。

题外话

跑通了最简单的场景,就可以向下一步更复杂的场景探索了。

结语

至此一个最简单的流程已经分析完毕。接下来,需要考虑的就是表单验证,数据反显(从后端拿到数据渲染编辑页面)等等,一步一个脚印,慢慢来吧。

相关文章:

  • [HDU5685]Problem A
  • 修改asm中的sys密码
  • “Master”连胜世界围棋冠军,谁是幕后智能引擎?
  • JSOUP 超时分析与处理
  • Ubuntu14.04如何用root账号登陆系统
  • 【翻译】关于Apache Flume FileChannel
  • “小小科技女神”与微软DigiGirlz Day的约会
  • 17-思科防火墙:ASA动态NAT:实验一
  • 字符串拼接的双引号和单引号问题,转义字符
  • 2018年7月7日笔记
  • ffmpeg用法(心得体会还有你见过的用法)
  • Spring Cloud Spring Boot mybatis分布式微服务云架构 返回JSON格式
  • 常用命令参考
  • HongCMS 审计学习
  • Mastering KVM Virtualization:第二章 KVM内部原理
  • css系列之关于字体的事
  • Java|序列化异常StreamCorruptedException的解决方法
  • JavaScript 是如何工作的:WebRTC 和对等网络的机制!
  • Leetcode 27 Remove Element
  • Three.js 再探 - 写一个跳一跳极简版游戏
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • 翻译 | 老司机带你秒懂内存管理 - 第一部(共三部)
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • 技术攻略】php设计模式(一):简介及创建型模式
  • 码农张的Bug人生 - 见面之礼
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 入门级的git使用指北
  • 阿里云ACE认证之理解CDN技术
  • 阿里云重庆大学大数据训练营落地分享
  • ​configparser --- 配置文件解析器​
  • ​你们这样子,耽误我的工作进度怎么办?
  • ​人工智能书单(数学基础篇)
  • $.ajax中的eval及dataType
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • (1)(1.13) SiK无线电高级配置(五)
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (阿里云万网)-域名注册购买实名流程
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (转)jQuery 基础
  • (转)项目管理杂谈-我所期望的新人
  • ****** 二 ******、软设笔记【数据结构】-KMP算法、树、二叉树
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .Net 4.0并行库实用性演练
  • .Net下的签名与混淆
  • @Valid和@NotNull字段校验使用
  • [ vulhub漏洞复现篇 ] JBOSS AS 4.x以下反序列化远程代码执行漏洞CVE-2017-7504
  • [ 云计算 | AWS ] 对比分析:Amazon SNS 与 SQS 消息服务的异同与选择
  • [Angular 基础] - 数据绑定(databinding)
  • [BZOJ] 2044: 三维导弹拦截