zoukankan      html  css  js  c++  java
  • 基于react hooks,antd v4 配置生成表单

    react后台项目,大多都是表单处理,比如下列4种常见1*n布局 (如果手工编码,大量的Row,Col, Form.Item的嵌套,排列,如果加上联动处理,代码将十分臃肿,不易维护)

    1. 一行一列
      form1.png

    2. 一行两列
      fom2.png

    3. 一行三列
      form3.png

    4. 一行四列
      form4.png

    对于这列表单开发, 完全可以基于配置生成, 我们可以定义一个数组,数组的每一项都是一个表单项, 对于一行一列的排列, 我们可以自上而下一行一个组件进行render , 伪代码如下

           return arr.map((item, idx) => itemRender(item, idx, 24))
    

    对于一行n列 (n=2/3/4 , 参考antd grid布局, 一行最多不超过4个表单项 https://ant.design/components/grid-cn/
    基于24 栅格系统,可以我们计算出每个组件占用的栅格数24/n , 基于此,我们可以动态创建Grid,自上而下一行一组进行render实现一行多列布局 ,伪代码如下

            const len = group.length;
            const span = 24 / len;
    
            return (
              <Row key={idx}>
                {arr.map((item, subIndex) => itemRender(item, subIndex, span))}
              </Row>
            );
    

    上述 itemRender 用于render表单组件, antd4表单项通常这么写 ,一个Form.Item 包裹一个表单控件,参考如下

        <Form.Item
            label="Username"
            name="username"
            rules={[{ required: true, message: 'Please input your username!' }]}
          >
            <Input />
          </Form.Item>
    

    基于js我们可以抽离出如下配置项, 1. render什么组件,例如Input/Select/等,另外配置组件props 2. Form.Item 配置 , 我们可以设计如下通用js对象表示这些信息

      {
        type?: React.ComponentType | string; // 组件类型, 比如Input 等
        name?: string; //Form.Item的name
        label?: string; // Form.Item的label
        elProps?: Record<string, unknown>; // 组件的props配置 , 比如type为Input, elProps则会配置到Input
        itemProps?: Record<string, unknown>;  // Form.Item的props配置,除了上面name,lable,rules三个常用的,其他的可以放在这里配置
        rules?: Rule[]; // Form.Item的rules
    };
    

    根据上面的js对象配置信息,我们可以实现itemRender动态创建组件和布局

    const itemRender = (item: Item, key: number | string, span = 24) => {
      if (typeof item !== 'object' || !item) return null;
    
      const { type, name, rules, label, elProps = {}, itemProps = {}, render, ...props } = item;
    
      return (
        <Col span={span} key={key}>
         <Form.Item name={name} label={label} rules={rules} {...itemProps}>
              {React.createElement(type, { ...props, ...elProps } as React.Attributes)}
            </Form.Item>
        </Col>
      );
    };
    

    为了更方便实现表单联动和支持render任意组件(不仅仅是表单), 我们可以扩展js加上render和getJSON 方法(当然叫getConfigJs更合适)

     {
        type?: React.ComponentType | string; // 组件类型, 比如Input 等
        name?: string; //Form.Item的name
        label?: string; // Form.Item的label
        render?: () => React.ReactNode; //自定义 render
        getJSON?: () => Item | null; // 动态返回Item配置
        elProps?: Record<string, unknown>; // 组件的props配置 , 比如type为Input, elProps则会配置到Input
        itemProps?: Record<string, unknown>;  // Form.Item的props配置,除了上面name,lable,rules三个常用的,其他的可以放在这里配置
        rules?: Rule[]; // Form.Item的rules
    };
    

    自此, 一个通用的antd form-render 就编写完了, 可以参考https://github.com/leonwgc/antd-form-render

    安装

    npm / yarn 安装:

    $ npm install --save antd-form-render
    $ yarn add antd-form-render
    

    功能

    • 配置一维数组实现 1 行 n 列 (自动布局,自上向下,自左向右布局,参考汽车自动挡驾驶) n可以是1/2/3/4 ,默认1
    • 配置二维数组实现 1行n列 (手动布局,每一行显示几列根据数组长度决定,参考汽车手动挡驾驶)
    • 天然支持表单联动 ,参考下面示例1性别选择代码
    • 支持自定义render, 当antd组件无法满足需求,可以自定义返回任意react node
    • 支持动态返回js对象, 参考下面示例1性别选择代码,性别男下面动态生成输入框,女则为下拉框
    • 数据收集,name作为key ,相应表单控件的值为value

    实现 1 行 1 列

    import React, { useState } from 'react';
    import FormRender from 'antd-form-render';
    import { Form, Button, Space, Input, Radio, Select } from 'antd';
    
    export default function App() {
      const [data, setData] = useState({});
    
      // 定义form
      const [form] = Form.useForm();
    
      // 一维数组定义layout,从上往下一行放一个表单控件
      const layout = [
        {
          type: Input,
          label: '手机号',
          placeholder: '请输入',
          name: 'tel',
          // 对Input的配置 , elProps对type指定的组件配置
          elProps: {
            maxLength: 11,
          },
          // 对Form.Item的配置
          itemProps: {
            rules: [
              { required: true, message: '请输入' },
              { pattern: /^1d{10}$/, message: '手机号必须为11位数字' },
            ],
          },
        },
        {
          type: Input.Password,
          label: '密码',
          placeholder: '请输入',
          name: 'pwd',
          itemProps: {
            rules: [{ required: true, message: '请输入' }],
          },
        },
        {
          type: Input.Password,
          label: '确认密码',
          placeholder: '请输入',
          name: 'confirmPwd',
          itemProps: {
            rules: [
              { required: true, message: '请输入' },
              ({ getFieldValue }) => ({
                validator(_, value) {
                  if (!value || getFieldValue('pwd') === value) {
                    return Promise.resolve();
                  }
                  return Promise.reject(new Error('两次密码不一致'));
                },
              }),
            ],
          },
        },
        {
          type: Radio.Group,
          label: '性别',
          name: 'gender',
          elProps: {
            options: [
              { label: '男', value: '男' },
              { label: '女', value: '女' },
            ],
          },
        },
        {
          // 根据条件动态返回object
          getJSON() {
            return data.gender === '男'
              ? {
                  type: Input,
                  label: '兴趣爱好(男)',
                  placeholder: '请输入兴趣爱好',
                  name: 'hobby',
                  itemProps: {
                    rules: [{ required: true, message: '请输入兴趣爱好' }],
                  },
                }
              : data.gender === '女'
              ? {
                  type: Select,
                  label: '兴趣爱好(女)',
                  placeholder: '请选择兴趣爱好',
                  name: 'hobby',
                  itemProps: {
                    itemProps: {
                      rules: [{ required: true, message: '请选择兴趣爱好' }],
                    },
                  },
                  elProps: {
                    options: [
                      { label: '画画', value: '画画' },
                      { label: '唱歌', value: '唱歌' },
                      { label: '跳舞', value: '跳舞' },
                    ],
                  },
                }
              : null;
          },
        },
        {
          type: Input.TextArea,
          name: 'desc',
          label: '简介',
          elProps: {
            placeholder: '个人简介',
            rows: 4,
          },
          itemProps: {
            rules: [
              {
                required: true,
              },
            ],
          },
        },
        {
          // 自定义render
          render() {
            return (
              <Form.Item>
                <Space>
                  <Button htmlType="submit" type="primary">
                    确定
                  </Button>
                  <Button htmlType="reset">重置</Button>
                </Space>
              </Form.Item>
            );
          },
        },
      ];
    
      return (
        <Form
          form={form}
          onValuesChange={(v) => {
            setData((p) => ({ ...p, ...v }));
          }}
        >
          <FormRender layoutData={layout} />
        </Form>
      );
    }
    

    实现 1 行 n 列如下 ,比如一行 2 列(子数组的长度决定列数,长度能被 24 整除)

    const layout = [
      [
        {
          type: Input,
          label: '11',
          placeholder: '请输入',
          name: '11',
        },
        {
          type: Input,
          label: '12',
          placeholder: '请输入',
          name: '12',
        },
      ],
      [
        {
          type: Input,
          label: '21',
          placeholder: '请输入',
          name: '21',
        },
        {
          type: Input,
          label: '22',
          placeholder: '请输入',
          name: '22',
        },
      ],
    ];
    

    实现 1 行 2/3/4 列如下

    // 一维数组,设置了cols 为1/2/3/4 ,实现自动从左至右,从上到下的 1*cols 1行多列自动布局
    
    const layout3 = [];
    
    for (let i = 0; i < 11; i++) {
      layout3.push({
        type: Input,
        label: `输入框${i + 1}`,
        placeholder: '请输入',
        name: `name${i}`,
      });
    }
    
    <FormRender layoutData={layout3} cols={cols}></FormRender>;
    

    配置说明

    // 组件
    export default function FormRenderer({ layoutData, cols }: FormRenderProps): React.ReactNode;
    
    // 组件props
    export declare type FormRenderProps = {
        layoutData: Array<Item>; // 1/2维数组
        cols: null | 1 | 2 | 3 | 4; // 自动布局1行显示几列 default 1
    };
    
    // 数组配置项
    export declare type Item = {
        type?: React.ComponentType | string; // 组件类型, 比如Input 等
        name?: string; //Form.Item的name
        label?: string; // Form.Item的label
        render?: () => React.ReactNode; //自定义 render
        getJSON?: () => Item | null; // 动态返回Item配置
        elProps?: Record<string, unknown>; // 组件的props配置 , 比如type为Input, elProps则会配置到Input
        itemProps?: Record<string, unknown>;  // Form.Item的props配置,除了上面name,lable,rules三个常用的,其他的可以放在这里配置
        rules?: Rule[]; // Form.Item的rules
    };
    
    

    运行示例, yarn start / npm start 查看 demo , 效果如下

    antd-form-render.png

  • 相关阅读:
    Android 程序员不得不收藏的个人博客(持续更新...)
    硬核讲解 Jetpack 之 LifeCycle 源码篇
    秉心说,不一样的 2019
    秉心说 2019 博文合集
    庖丁解牛 Activity 启动流程
    Jetpack Compse 实战 —— 全新的开发体验
    Box 黑科技 —— 支持手机端反编译 !
    “无处不在” 的系统核心服务 —— ActivityManagerService 启动流程解析
    如何正确的在 Android 上使用协程 ?
    【Medium 万赞好文】ViewModel 和 LIveData:模式 + 反模式
  • 原文地址:https://www.cnblogs.com/leonwang/p/14746135.html
Copyright © 2011-2022 走看看