rc-form之最单纯情况

A_大白

前言

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

探究内容

效果

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

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中值,返回被修改后的组件的流程。

题外话

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

结语

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

阅读 13.2k

大白的专栏
记录各种新技能get

Alibaba-高德,信息前端持续招人中,欢迎私信我,或者钉钉pws019。

1k 声望
68 粉丝
0 条评论

Alibaba-高德,信息前端持续招人中,欢迎私信我,或者钉钉pws019。

1k 声望
68 粉丝
宣传栏