React封装动态表单 <DynamicForm />

816 阅读2分钟

动态表单-DynamicForm

一. 需求点

每次重复搬antd组件会很烦,如果创造一个,动态表单生成器,只需传入配置,就会生成Form组件,达到提效的作用那

dynamicFormFlow.png

二. 能力拆分

生成器 - MatchingNode

需要一个根据外界传入type(表单组件类型),就能生成对应antd组件的生成器

组合器 - CombineForm

需要一个把静态属性 和 动态属性组装成一体的组合器

合规配置

表单配置项数据格式要符合antd的传参方式

三. MatchingNode

根据传入的type匹配对应的生成类型

'text''input''inputNumber''radio''slider''formList''submit'
文本<Input/><InputNumber/><Radio/><Slider/><Form.List/><Button />
匹配节点
switch(type) {
    case 'text':
        return renderCondition && renderCondition();
    case 'input':
        return renderCondition && renderCondition(<Input {...nodeProps} />);
    case 'inputNumber':
        return renderCondition && renderCondition(<InputNumber {...nodeProps} />);
    case 'select':
        return renderCondition && renderCondition(
            <Select {...nodeProps}>
                {
                    childrenData?.map((chil, index) => <Select.Option key={index} value={chil?.value}>{chil?.text}</Select.Option>)
                }
            </Select>
        );
    case 'radio':
        return renderCondition && renderCondition(
            <Radio.Group {...nodeProps}>
                {
                    childrenData && childrenData?.map((chil) => <Radio value={chil?.value}>{chil?.text}</Radio>) || null
                }
            </Radio.Group>
        );
    case 'slider':
        return renderCondition && renderCondition(
          <Row>
            <Col span={12}>
              <Slider {...nodeProps?.slider} />
            </Col>
            <Col span={4}>
              <InputNumber {...nodeProps?.inputnumber} />
            </Col>
          </Row>
        );
    case 'formList':
        return renderCondition && renderCondition(
          <Form.List {...nodeProps}>
            {(fields, { add, remove }) => (
              <>
                {fields.map(({ key, name, ...restField }) => (
                  <Space key={key} {...children?.spaceStyle}>
                    {
                      childrenData?.data?.map((item) => (
                        <Form.Item
                          {...restField}
                          name={[name, item?.name]}
                          label={item?.label}
                        >               
                          <Input {...item?.childrenProps} />
                        </Form.Item>
                      ))
                    }
                    <MinusCircleOutlined onClick={() => remove(name)} />
                  </Space>
                ))}
                <Form.Item>
                  <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
                    {childrenData?.buttonName}
                  </Button>
                </Form.Item>
              </>
            )}
          </Form.List>
        );
    case 'submit':
        return renderCondition && renderCondition(
          <Space>
            {
              childrenData?.map(item => {
                return item?.renderCondition(
                  <Button {...item.buttonProps}>
                    {item.text}
                  </Button>
                );
              })
            }
          </Space>
        );
     default:
         return other...
};
生成 Form 表单
return (
     <Form {...config?.propertys}>
        {
            config?.items?.map((item) => (
                 <Form.Item 
                     {...item?.propertys}
                 >		
                    { 
                       matchingNode(item) 
                    }	
                 </ Form.Item>
            ))
        }
    </Form>
)

四. CombineForm

接收静态属性StaticAttributes 和 动态属性DynamicAttributes,将其组合成完整属性,提供给MatchingNode

静态属性
const environmentFormConfig: EnvironmentFormConfig = {
    propertys: {
        name: 'Environments',
        labelCol: { span: 4 },
        wrapperCol: { span: 14 },
        autoComplete: 'off',
        ...formPropertys
    },
    items: [	
        {
            propertys: {
                label: 'Software project',
                name: 'appName',
            },
            children: {},
        },
        {
            propertys: {
                label: 'Name',
                name: 'name',
                rules: [{ required: true, message: 'Please input your name!' }]
            },
            children: {},
        }
    ],
    ......
};
合并属性
chilDynamicAttributes?.forEach((ele, index) => {
    const originChildrenProp = staticAttributes.items[index].children;
    staticAttributes.items[index].children = {...originChildrenProp, ...ele};
});
return staticAttributes;

五. 合规传参数

最终要生成渲染antd组件,所以参数配置要区分出来,哪些是用来匹配组件类型的,哪些props是antd组件可直接识别的,哪些props是渲染子组件的数据,例如Selection.OptionRadio.Group -> Radio。 renderCondition()函数是 渲染条件,用处是,新增(add)和编辑(edit)在共用表单组件时,要根据路由参数、或外层组件传入的关键字区别是新增 还是 编辑,表单项会显示不同的内容,此时通过渲染函数可做预处理逻辑

const chilDynamicAttributes = [
    {
        type: 'text',
        renderCondition: () => appName,
    },
    {
        type: 'input',
        renderCondition: (node) => unDetails ? node : initialValues?.name,
    },
    {
        type: 'radio',
        renderCondition: (node) => unDetails ? node : initialValues?.envGrade,
        nodeProps: {
            onChange: handleGrade,
        },
        childrenData: gradeDatas	
    },
    {
        type: 'select',
        renderCondition: (node) => unDetails ? node : initialValues?.unitCode,
        nodeProps: {
            placeholder: "Select Deploy Unit",
            onChange: (value) => setCurrentUnit(value),
            allowClear: true
        },
        childrenData: deployUnit
    },
    {
        type: 'formList',
        renderCondition: (node) => node,
        nodeProps: {
            name: 'envVars'
        },
        spaceStyle: {
            style: {
                display: 'flex',
                marginBottom: 8
            },
            align: "baseline"
        },
        childrenData: {
            data: [
                {
                    name: 'name',
                    label: '名称',
                    childrenProps: {
                        disabled: pathType === 'detail' && !detailEdit,
                        placeholder: 'name'
                    }
                },
                {
                    name: 'value',
                    label: '引用',
                    childrenProps: {
                        disabled: pathType === 'detail' && !detailEdit,
                        placeholder: 'value'
                    },
                },
             ],
             buttonName: '添加变量'
        },
      },
      {
          type: 'submit',
          renderCondition: (node) => node,
          childrenData: [
              {
                  renderCondition: (node) => !unDetails && node,
                  buttonProps: {
                      type: 'primary',
                      onClick: () => setDetailEdit(!detailEdit)
                  },
                  text: detailEdit ? 'Details' : 'Edit'
              },
              {
                  renderCondition: (node) => node,
                  buttonProps: {
                      type: 'primary',
                      htmlType: "submit"
                  },
                 text: 'Submit'
              },
          ],
      },
];