近期准备升级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;
在线预览
区别:
接下来我们深入源码,看一下 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