Form.Item 中设置了 name 属性,但是 Form 中的 onValuesChange 并没有生效。简单代码如下,可以看 codesanbox[1] 示例:
const schemaList = [
{
label: "Name",
field: "name",
component: Input,
rules: [{ required: true, message: "Name is required" }],
props: {
showCount: true,
maxLength: 30
}
}
];
const Demo = () => {
const [form] = Form.useForm();
const onValueChange = () => {
console.log("test");
};
return (
<Form name="basic" form={form} onValuesChange={onValueChange}>
{schemaList.map((item) => {
// 方法二:修改
// const component = getBasicFormItem(form.getFieldsValue(true), item);
return (
<Form.Item label={item.label} name={item.field} rules={item.rules}>
{/* 方法二修改 */}
{/* {component} */}
<BasicFormItem form={form.getFieldsValue(true)} schema={item} />
</Form.Item>
);
})}
</Form>
);
};
BasicFormItem 的代码如下:
const BasicFormItem = ({ form, schema }) => {
if (schema.component) {
const Component = schema.component;
return <Component {...schema.props}></Component>;
} else {
return form[schema.field] !== undefined ? form[schema.field] : "-";
}
};
尝试将上面的 function Component 写成一个返回组件的 function
const getBasicFormItem = (form, schema) => {
if (schema.component) {
const Component = schema.component;
return <Component {...schema.props}></Component>;
} else {
return form[schema.field] !== undefined ? form[schema.field] : "-";
}
};
// 调用的时候返回一个组件
const component = getBasicFormItem(form.getFieldsValue(true), item);
return (
<Form.Item label={item.label} name={item.field} rules={item.rules}>
{/* 方法二修改 */}
{component}
<BasicFormItem form={form.getFieldsValue(true)} schema={item} />
</Form.Item>
);
这其实是一种比较 hack 的方法,而且每次都一定会去执行这个 function,返回一个全新的 component,可能会存在一些性能问题
其实官方[2]也有提到
被设置了 name 属性的 Form.Item 包装的控件,表单控件会自动添加 value(或 valuePropName 指定的其他属性) onChange(或 trigger 指定的其他属性),数据同步将被 Form 接管。这会导致以下结果:
1.你不再需要也不应该用 onChange 来做数据收集同步(你可以使用 Form 的 onValuesChange),但还是可以继续监听 onChange 事件。
2.你不能用控件的 value 或 defaultValue 等属性来设置表单域的值,默认值可以用 Form 里的 initialValues 来设置。注意 initialValues 不能被 setState 动态更新,你需要用 setFieldsValue 来更新。
3.你不应该用 setState,可以使用 form.setFieldsValue 来动态改变表单值。
但在上面 BasicFormItem 中,我只接收了 form 和 schema 参数,所以并没有生效,所以可以修改成如下:
- const BasicFormItem = ({ form, schema }) => {
+ const BasicFormItem = ({ form, schema, ...reset }) => {
if (schema.component) {
const Component = schema.component;
- return <Component {...schema.props}></Component>;
+ return <Component {...reset} {...schema.props}></Component>;
} else {
return form[schema.field] !== undefined ? form[schema.field] : "-";
}
};
这样就可以了
问题来了,antd 是怎么做到将 value 和 onChange 注入的呢?
问题的答案在于:cloneElement()[3]
以 element 元素为样板克隆并返回新的 React 元素。config 中应包含新的 props,key 或 ref。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。
React.cloneElement() 几乎等同于:
<element.type {...element.props} {...props}>{children}</element.type>
以下为一个大神的简单版实现,可以看下,详情[4]
核心代码的实现如下:
let wrapperNode: React.ReactNode = React.cloneElement(
children as React.ReactElement,
{
onChange(event) {
if (event && event.target) {
const newValue = (event.target as HTMLInputElement)['value'];
onFieldChange!(name, newValue);
}
},
},
);
这里就将 onChange 注入到子组件的 props 中了,然后变化的时候,再通知 Form 组件进行相应的更新
[1]codesanbox: https://codesandbox.io/s/ji-ben-shi-yong-antd-4-19-5-forked-rxwn8f?file=/index.js:1256-1877
[2]官方: https://ant.design/components/form-cn/#Form.Item
[3]cloneElement(): https://zh-hans.reactjs.org/docs/react-api.html#cloneelement
[4]详情: https://codesandbox.io/s/fragrant-resonance-ghkbj1?file=/src/Form/Field.tsx
[5]难道没人对Form.Item如何处理Input感兴趣么: https://zhuanlan.zhihu.com/p/412418736
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8