rc-form之最單純狀況

前言

第一次探索這個框架,對於裏面不少邏輯是不懂的,因此只能一點一點去揣摩,其中作了什麼。
而學習過程當中,老是禁不住好奇這裏的邏輯是幹什麼的,那裏的邏輯是什麼的,在不理解這段邏輯是作什麼的狀況下,死磕很容易事倍功半。因此本次先從一個比較簡單的場景入手,看看它的源碼中作了什麼手腳,至於有些邏輯沒有涉及到的,先不去管它就行了。react

探究內容

效果

首先上圖,看看此次案例的效果。後端

clipboard.png

實際上是上一篇案例的精簡版,去掉了非空驗證。可是分析的更細緻些。app

業務代碼

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中作了什麼。this

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值得咱們關注spa

// 簡化後的內容以下
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中值,返回被修改後的組件的流程。

題外話

跑通了最簡單的場景,就能夠向下一步更復雜的場景探索了。

結語

至此一個最簡單的流程已經分析完畢。接下來,須要考慮的就是表單驗證,數據反顯(從後端拿到數據渲染編輯頁面)等等,一步一個腳印,慢慢來吧。

相關文章
相關標籤/搜索