antd3和4的form對比

近期準備升級antd4.0開發,發現form組件改動非常大,於是抽空看了改動點,於是有了這篇文章:


首先回顧一下antd3的用法

import React from "react";
import { Form, Input, Button } from "antd";

const Demo = ({ form }) => {
  const { getFieldDecorator, validateFields } = form;
  const handleSubmit = e => {
    e.preventDefault();
    validateFields((err, values) => {
      if (!err) {
        console.log(values);
      }
    });
  };
  return (
    <Form layout="inline" onSubmit={handleSubmit}>
      <Form.Item>
        {getFieldDecorator("username", {
          initialValue: '',       
          rules: [{ required: true, message: "Please input your username!" }]
        })(<Input />)}
      </Form.Item>
      <Form.Item>
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
      </Form.Item>
    </Form>
  );
};
export default Form.create()(Demo);

在線預覽


接下來是 antd4 的用法

import React from "react";
import { Form, Input, Button } from "antd";

const Demo = () => {
  const onFinish = values => {
    console.log(values);
  };

  return (
    <Form
      name="basic"
      layout="inline"
      initialValues={{ username: '' }}
      onFinish={onFinish}
    >
      <Form.Item
        label="姓名"
        name="username"
        rules={[{ required: true, message: "Please input your username!" }]}
      >
        <Input />
      </Form.Item>

      <Form.Item>
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
      </Form.Item>
    </Form>
  );
};

export default Demo;

在線預覽


區別:

  1. 不需要Form.create包裝
  2. 校驗成功後的values通過onFinish可以直接獲取
  3. 不需要getFieldDecorator包裝,name rules initialValues 作爲組件屬性傳遞


接下來我們深入源碼,看一下 antd 4 的form是怎麼玩?


問題一:爲啥不再需要 Form.create 包裝?

首先是創建了一個 FormStore 類,當在Form中引用自定義hooks的時候直接new了FormStore的實例,然後調用了實例的getForm方法,將類上掛載的方法賦值給formRef.current返回,這樣Form就可以通過引入的FormStore實例調用類上的各種方法來操作了。

// useForm.ts
export class FormStore {
  private store: Store = {};
  ...
	public getForm = (): InternalFormInstance => ({
    getFieldValue: this.getFieldValue,
    getFieldsValue: this.getFieldsValue,
    getFieldError: this.getFieldError,
    getFieldsError: this.getFieldsError,
    isFieldsTouched: this.isFieldsTouched,
    isFieldTouched: this.isFieldTouched,
    isFieldValidating: this.isFieldValidating,
    isFieldsValidating: this.isFieldsValidating,
    resetFields: this.resetFields,
    setFields: this.setFields,
    setFieldsValue: this.setFieldsValue,
    validateFields: this.validateFields,
    submit: this.submit,

    getInternalHooks: this.getInternalHooks,
  });
	...
}

function useForm(form?: FormInstance): [FormInstance] {
  const formRef = React.useRef<FormInstance>();
  const [, forceUpdate] = React.useState();

  if (!formRef.current) {
    if (form) {
      formRef.current = form;
    } else {
      // Create a new FormStore if not provided
      const forceReRender = () => {
        forceUpdate({});
      };

      const formStore: FormStore = new FormStore(forceReRender);

      formRef.current = formStore.getForm();
    }
  }

  return [formRef.current];
}


問題二:antd3 是不支持校驗函數組件,antd4是如何處理的?
**
Form中如果包裹函數組價,會直接執行並將結果賦值給childrenNode,Field中會通過遞歸執行函數組價,並且只會返回第一個children。

// Form對函數組價的處理
// Prepare children by `children` type
let childrenNode = children;
const childrenRenderProps = typeof children === 'function';
if (childrenRenderProps) {
  const values = formInstance.getFieldsValue(true);
  childrenNode = (children as RenderProps)(values, formInstance);
}


// Field 對函數組價的處理
class Field extends React.Component {
	public render() {
    ...
    const { child, isFunction } = this.getOnlyChild(children);
    ...
    return <React.Fragment key={resetCount}>{returnChildNode}</React.Fragment>;
  }
}

// Only return validate child node. If invalidate, will do nothing about field.
public getOnlyChild = (
  children:
    | React.ReactNode
    | ((control: ChildProps, meta: Meta, context: FormInstance) => React.ReactNode),
): { child: React.ReactNode | null; isFunction: boolean } => {
  // Support render props
  if (typeof children === 'function') {
    const meta = this.getMeta();

    return {
      ...this.getOnlyChild(children(this.getControlled(), meta, this.context)),
      isFunction: true,
    };
  }

  // Filed element only
  const childList = toChildrenArray(children);
  if (childList.length !== 1 || !React.isValidElement(childList[0])) {
    return { child: childList, isFunction: false };
  }

  return { child: childList[0], isFunction: false };
};


問題三:onFinish運行邏輯,爲啥不需要validateFields獲取屬性了?
**
之前校驗有兩種方法,一種是直接在button上添加onclick事件,一種是設置htmlType=“submit”,觸發form上的onSubmit,無論哪一種,最後都是調用了validateFields方法,但那是antd 4.0 之後在form上添加了onFinish方法,內部調用了validateFields,校驗通過後會觸發onFinish方法,具體看下面代碼:

// useForm.ts
private submit = () => {
  this.warningUnhooked();

  this.validateFields()
    .then(values => {
      const { onFinish } = this.callbacks;
      if (onFinish) {
        try {
          onFinish(values);
        } catch (err) {
          // Should print error if user `onFinish` callback failed
          console.error(err);
        }
      }
    })
    .catch(e => {
      const { onFinishFailed } = this.callbacks;
      if (onFinishFailed) {
        onFinishFailed(e);
      }
    });
};


問題四:initialValues 爲啥只會首次觸發,後面值發生改變,頁面依然是老值?
**
useForm 是自定義的hooks,form類就在這裏實現,然後導入到了Form組件中,首次執行的mountRef.current 不存在,所以會執行 setValues({}, initialValues, this.store),將初始值掛載到store上,但是再次執行的時候 mountRef.current 已經爲 true,掛載的邏輯就不會執行,所以這時即使initialValues發生了變化,但是store中的值已經不變,頁面中的值是從store中取的自然不然改變。

// Form.tsx
// Set initial value, init store value when first mount
const mountRef = React.useRef(null);
setInitialValues(initialValues, !mountRef.current);
if (!mountRef.current) {
  mountRef.current = true;
}


// useForm.ts
/**
 * First time `setInitialValues` should update store with initial value
 */
private setInitialValues = (initialValues: Store, init: boolean) => {
  this.initialValues = initialValues || {};
  if (init) {
    this.store = setValues({}, initialValues, this.store);
  }
};


問題五:如何在antd3中使用

import React from 'react';
import { Input, Button, Col } from 'antd';
import Form, { Field } from 'rc-field-form';

function App() {
  return (
    <Form
      onFinish={(values) => {
        console.log(values);
      }}
      initialValues={{ name: 'iwen' }}
    >
      <Col span={8}>
        <Field name="name">
          <Input />
        </Field>
      </Col>
      <Button type="primary" htmlType="submit">
        Submit
      </Button>
    </Form>
  );
}

export default App;

在線預覽




總結:antd4 的form基本上重寫,將以前很多需要使用者手動調用的方法,比如Form.create,getFieldDecorator,validateFields都內置化,使用起來相比antd3使用確實方便了不少。


參考:https://github.com/react-component/field-form/tree/v1.4.0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章