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

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