Antd 中 Form.Item name 属性不生效问题

846次阅读  |  发布于2年以前

问题

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