问题
Form.Item 中设置了 name 属性,但是 Form 中的 onValuesChange 并没有生效。简单代码如下,可以看 codesanbox 示例:
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,可能会存在一些性能问题
解决方法二
其实官方也有提到
被设置了 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()
以 element 元素为样板克隆并返回新的 React 元素。config 中应包含新的 props,key 或 ref。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。
React.cloneElement() 几乎等同于:
<element.type {...element.props} {...props}>{children}</element.type>
以下为一个大神的简单版实现,可以看下,详情
核心代码的实现如下:
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 组件进行相应的更新
参考
难道没人对Form.Item如何处理Input感兴趣么
原文:https://juejin.cn/post/7103472687170715684