近期準備升級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;
在線預覽
區別:
- 不需要Form.create包裝
- 校驗成功後的values通過onFinish可以直接獲取
- 不需要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