antd4 源碼學習 :表單


首先。vue 的數據流是雙向的,而 react 的數據流是單向的。

這意味着什么?
這意味着,vue 中,子組件可以用 emit 把數據更新傳給父組件。而 react 中, 需要通過父組件把回調函數傳給子組件實現類似功能。
 
為什么要說這個?因為框架的設計會影響到組件庫的設計。組件庫的設計必須配合框架。
 
我們回憶一下, antd3 中表單是怎么用的?
我們需要傳入 onSubmit 回調函數,去做表單提交操作。
為什么需要傳入這玩意?因為正如上面所說, 需要通過父組件把回調函數傳給子組件實現類似功能
當我們使用 Form 組件的時候,頁面就是父組件。Form 就是子組件。
我們把在頁面文件里寫的方法傳給 Form 的 onSubmit ,它把這個方法綁定在表單的原生提交事件上,實現以上功能。
 

 
我們來看一下 antd3 的 Form.create。
 
Form.create(options)(Component)#
使用方式如下:
class CustomizedForm extends React.Component {}
 
CustomizedForm = Form.create({})(CustomizedForm);
 
然后,我們來看一下 antd3 的 this.props.form.getFieldDecorator(id, options)。

this.props.form.getFieldDecorator(id, options)#

經過getFieldDecorator包裝的控件,表單控件會自動添加value(或valuePropName指定的其他屬性)onChange(或trigger指定的其他屬性),數據同步將被 Form 接管,這會導致以下結果:
不再需要也不應該onChange來做同步,但還是可以繼續監聽onChange等事件。
你不能用控件的valuedefaultValue等屬性來設置表單域的值,默認值可以用getFieldDecorator里的initialValue
你不應該用setState,可以使用this.props.form.setFieldsValue來動態改變表單值。
 
源碼:
 
getFieldDecorator: function getFieldDecorator(name, fieldOption) {
        var _this2 = this;
 
 
        var props = this.getFieldProps(name, fieldOption);
        return function (fieldElem) {
          // We should put field in record if it is rendered
          _this2.renderFields[name] = true;
 
 
          var fieldMeta = _this2.fieldsStore.getFieldMeta(name);
          var originalProps = fieldElem.props;
          if (process.env.NODE_ENV !== 'production') {
            var valuePropName = fieldMeta.valuePropName;
            (0, _warning2['default'])(!(valuePropName in originalProps), '`getFieldDecorator` will override `' + valuePropName + '`, ' + ('so please don\'t set `' + valuePropName + '` directly ') + 'and use `setFieldsValue` to set it.');
            var defaultValuePropName = 'default' + valuePropName[0].toUpperCase() + valuePropName.slice(1);
            (0, _warning2['default'])(!(defaultValuePropName in originalProps), '`' + defaultValuePropName + '` is invalid ' + ('for `getFieldDecorator` will set `' + valuePropName + '`,') + ' please use `option.initialValue` instead.');
          }
          fieldMeta.originalProps = originalProps;
          fieldMeta.ref = fieldElem.ref;
          return _react2['default'].cloneElement(fieldElem, (0, _extends6['default'])({}, props, _this2.fieldsStore.getFieldValuePropValue(fieldMeta)));
        };
      },
 
fieldElem 就是我們傳進入的表單DOM。
props 是傳進去的各種選項(比如表單驗證)處理后的東西。
fieldMeta 是把傳進去的表單名(比如userName passWord)處理后的東西。
 
getFieldMeta:
function getFieldMeta(name) {
      this.fieldsMeta[name] = this.fieldsMeta[name] || {};
      return this.fieldsMeta[name];
    }
 
getFieldValuePropValue:
function getFieldValuePropValue(fieldMeta) {
      var name = fieldMeta.name,
          getValueProps = fieldMeta.getValueProps,
          valuePropName = fieldMeta.valuePropName;
 
 
      var field = this.getField(name);
      var fieldValue = 'value' in field ? field.value : fieldMeta.initialValue;
      if (getValueProps) {
        return getValueProps(fieldValue);
      }
      return (0, _defineProperty3['default'])({}, valuePropName, fieldValue);
    }
 
cloneElement 是 react 方法。
 
總的來說做了兩件事:
  • 把數據放入 Form.state ,便於之后的各種處理。
  • 把傳進去的 DOM 進行混入與克隆。
 

 
而在 antd4 中——
 
 
v4 的 Form 不再需要通過 Form.create() 創建上下文。Form 組件現在自帶數據域,因而 getFieldDecorator 也不再需要,直接寫入 Form.Item 即可:
Form 自帶表單控制實體,如需要調用 form 方法,可以通過 Form.useForm() 創建 Form 實體進行操作:
 
那么,什么叫自帶數據域
看源碼:
ant-design-master\components\form\Form.tsx :
return (
    <SizeContextProvider size={size}>
      <FormContext.Provider value={formContextValue}>
        <FieldForm
          id={name}
          {...restFormProps}
          onFinishFailed={onInternalFinishFailed}
          form={wrapForm}
          className={formClassName}
        />
      </FormContext.Provider>
    </SizeContextProvider>
  );
 
很容易得知 FieldForm 是核心。
 
rc-field-form\es\Form.js:
return React.createElement(Component, Object.assign({}, restProps, {
    onSubmit: function onSubmit(event) {
      event.preventDefault();
      event.stopPropagation();
      formInstance.submit();
    }
  }), wrapperNode);
 
 
參數 Component 是元素名稱、wrapperNode 是子元素DOM(常說成children)。
 
Component:
Component = _ref$component === void 0 ? 'form' : _ref$component,
 
wrapperNode:
var wrapperNode = React.createElement(_FieldContext.default.Provider, {
  value: formContextValue
}, childrenNode);
 
_FieldContext 是存放警告信息的,如果一切正常就什么也不做。
 
childrenNode:
var childrenNode = children;
children = _ref.children,
 
我們發現:
  • 當聲明 Form 的時候,會渲染 Form 元素。
  • 對於子元素基本上就是什么也不做。
 
為什么呢?
因為還有 Form.Item 。
 
ant-design-master\components\form\FormItem.tsx:
return (
      <Row
        className={classNames(itemClassName)}
        style={style}
        key="row"
        {...omit(restProps, [
          'colon',
          'extra',
          'getValueFromEvent',
          'getValueProps',
          'hasFeedback',
          'help',
          'htmlFor',
          'id', // It is deprecated because `htmlFor` is its replacement.
          'initialValue',
          'isListField',
          'label',
          'labelAlign',
          'labelCol',
          'normalize',
          'preserve',
          'required',
          'validateFirst',
          'validateStatus',
          'valuePropName',
          'wrapperCol',
        ])}
      >
        {/* Label */}
        <FormItemLabel htmlFor={fieldId} required={isRequired} {...props} prefixCls={prefixCls} />
        {/* Input Group */}
        <FormItemInput
          {...props}
          {...meta}
          errors={mergedErrors}
          prefixCls={prefixCls}
          onDomErrorVisibleChange={setDomErrorVisible}
          validateStatus={mergedValidateStatus}
        >
          <FormItemContext.Provider value={{ updateItemErrors: updateChildItemErrors }}>
            {baseChildren}
          </FormItemContext.Provider>
        </FormItemInput>
      </Row>
    );
 
  • baseChildren 只有出錯了才會出現,不用管。
  • Form.Item 一定會渲染 Col.
 
關鍵是 FormItemLabel 和 FormItemInput ,他們都會接收所有的 props 。
 
ant-design-master\components\form\FormItemLabel.tsx:
return (
          <Col {...mergedLabelCol} className={labelColClassName}>
            <label
              htmlFor={htmlFor}
              className={labelClassName}
              title={typeof label === 'string' ? label : ''}
            >
              {labelChildren}
            </label>
          </Col>
        );
 
  • 會渲染 Col.
  • 會渲染 label 元素。文字信息會放在 labelChildren 里面。
ant-design-master\components\form\FormItemInput.tsx:
return (
    <FormContext.Provider value={subFormContext}>
      <Col {...mergedWrapperCol} className={className}>
        <div className={`${baseClassName}-control-input`}>
          <div className={`${baseClassName}-control-input-content`}>{ children}</div>
          {icon}
        </div>
        <CSSMotion
          motionDeadline={500}
          visible={visible}
          motionName="show-help"
          onLeaveEnd={() => {
            onDomErrorVisibleChange(false);
          }}
          motionAppear
          removeOnLeave
        >
          {({ className: motionClassName }: { className: string }) => {
            return (
              <div className={classNames(`${baseClassName}-explain`, motionClassName)} key="help">
                {memoErrors.map((error, index) => (
                  // eslint-disable-next-line react/no-array-index-key
                  <div key={index}>{error}</div>
                ))}
              </div>
            );
          }}
        </CSSMotion>
        {extra && <div className={`${baseClassName}-extra`}>{extra}</div>}
      </Col>
    </FormContext.Provider>
  );
 
children 就是某個表單元素,比如 Input 。
 
那么,表單的數據域到底存在於什么地方?它是怎么被聲明的?
這必須說到兩個東西: createContext useContext.
 
先看這兩個東西在 antd4 中是如何被使用的吧。
 
ant-design-master\components\form\FormItem.tsx:
import { FormContext, FormItemContext } from './context';
 
const { name: formName } = React.useContext(FormContext);
const { updateItemErrors } = React.useContext(FormItemContext);
 
ant-design-master\components\form\context.tsx:
export const FormContext = React.createContext<FormContextProps>({
  labelAlign: 'right',
  vertical: false,
  itemRef: (() => {}) as any,
});
 
export interface FormItemContextProps {
  updateItemErrors: (name: string, errors: string[]) => void;
}
 
那么,Context 是個什么玩意?其實, 它是 react 的一個機制
官方介紹已經說的很清楚了——
 
在一個典型的 React 應用中,數據是通過 props 屬性自上而下(由父及子)進行傳遞的,但這種做法對於某些類型的屬性而言是極其繁瑣的(例如:地區偏好,UI 主題),這些屬性是應用程序中許多組件都需要的。Context 提供了一種在組件之間共享此類值的方式,而不必顯式地通過組件樹的逐層傳遞 props。
 
所以答案已經呼之欲出了。
表單的數據域會存在,而且不需要聲明。
因為它用的是 Context , 它沒有也不需要獨立的數據管理,表單容器的數據變化會直接反映到表單
function getFieldMeta(name) {
      this.fieldsMeta[name] = this.fieldsMeta[name] || {};
      return this.fieldsMeta[name];
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM