React之Form表单封装


前提说一下,我使用的ui组件是React-antd

一、form表单封装之树形选择框封装

树形选择框对应于antd的 https://ant.design/components/tree-select-cn/

1. 代码结构

(1)html代码

代码如下:

import React, { FC, forwardRef } from 'react';
import { MarketList } from 'utils/convertToTreeData';
import { TreeSelect } from 'antd';
import styles from './tree-picker.module.scss';
import { TreeSelectProps } from 'antd/lib/tree-select';
import { TreeNodeValue } from 'antd/lib/tree-select/interface';

//定义树形选择框所使用的prop
interface ComPonentProps<T extends TreeNodeValue> {
  style?: object;
  loading?: boolean;
  treeData: MarketList[];
  placeholder?: string;
  value?: TreeNodeValue;
  onChange?: TreeSelectProps<T>['onChange'];
}
type Props = ComPonentProps<TreeNodeValue>;
const TreePicker: FC<Props> = ({ onChange, value, style, loading, treeData, placeholder }, ref): React.ReactElement => {
  return (
    <>
      <TreeSelect
        className={styles['tree-select']} //css做样式调整
        loading={loading}
        style={style}
        treeData={treeData}
        treeCheckable={true}
        placeholder={placeholder}
        onChange={onChange}
        ref={ref}
        value={value}
      ></TreeSelect>
    </>
  );
};
export default forwardRef(TreePicker);

(2)树形选择框的结构

MarketList 的结构如下:

interface MarketItem {
  key: string;
  value: string;
  title: string;
}
export interface MarketList {
  key: string;
  value: string;
  title: string;
  children: MarketItem[];
}

(3)css

我用的是scss
css如下:

.tree-select {
  :global .ant-select-selection {
    height: 30px;
    overflow-y: scroll;
  }
}

(4)转化函数

注意在使用树形选择框时,需要数据转化为MarketList的形式,转化函数如下:
先调用father函数转化大致格式,再将其转化为树形选择框的形式

//以下函数对我的数据有用,对自己的数据可以改变形式
//我的数据格式如下:
/*
[
    {
        "group": {
            "id": "5f2b9d0832326e014cfe862a",
            "parentId": "5f2b9d0832326e014cfe862a",
            "rootId": "5f2b9d0832326e014cfe862a",
            "groupName": "测试设备"
        },
        "devices": [
            {
                "id": "5f4f8863afc2140186e27b17",
                "deviceId": "18:93:7F:B3:55:98",
                "deviceName": "444-2"
            },
            {
                "id": "5f63097fafc2140186e27b18",
                "deviceId": "18:93:7F:94:D4:4D",
                "deviceName": "333"
            },
            {
                "id": "5fa3ab7ff34acc03cf0d2c99",
                "deviceId": "18:93:7F:B3:34:94",
                "deviceName": "5555"
            },
            {...}
        ]
    },
    {...}
]
*/
//将数据结构统一
export function convertToFatherTreeData(list: any[]): MarketList[] {
  list.forEach(item => {
    item.deviceName = item.group.groupName;
    item.deviceId = item.group.rootId;
    item.id = item.group.id;
    item.children = item.devices;
    delete item.devices;
    delete item.group;
  });

  return list;
}
//将数据转成antd所需要的格式
export function convertToTreeData(list: any[]): MarketList[] {
  list.forEach(item => {
    item.title = item.deviceName;
    item.value = item.deviceId;
    item.key = item.id;
    delete item.deviceName;
    delete item.deviceId;
    delete item.id;
    if (item.children && item.children.length) {
      convertToTreeData(item.children);
    }
  });
  return list;
}

2. 使用方法

(1)单独使用

这里先介绍独立的使用方法

<Col span={6}>
  <label style={{ verticalAlign: 'top', lineHeight: '31px' }}>范围:</label>
  <TreePicker
    onChange={handleDeviceChange}
    loading={loading}
    treeData={deviceArea} //转化好的数据,这里就不做赘述
    style={{ width: '70%' }}
    placeholder={'请选择范围'}
  />
</Col>

onchange函数

//可以将数据直接保存在state数组中
  const handleDeviceChange = useCallback(
    value => {
      setDeviceGroup(value);
    },
    [setDeviceGroup],
  );

(2)和form表单一起使用

这里不使用onChange函数,可以使用form的onSubmit/onFinish函数就可以拿到group的值

  <Form.Item label={'范围'}  >
     {getFieldDecorator(`group`, {
       initialValue: group,//默认数据
     })(
       <TreePicker
         loading={loading}
    	 treeData={deviceArea} //转化好的数据,这里就不做赘述
         placeholder={ `请选择范围`}
       />,
     )}
   </Form.Item>

二、form表单之时间范围选择器封装

1. 代码

(1)html代码

import React, { FC, useCallback, forwardRef } from 'react';
import moment from 'moment';
import { DatePicker } from 'antd';
import { RangePickerValue, RangePickerProps } from 'antd/lib/date-picker/interface';

const { RangePicker } = DatePicker;

interface ComPonentProps {
  style?: object;
  value?: RangePickerValue;
  onChange?: RangePickerProps['onChange'];
}
type Props = ComPonentProps;

const TimePicker: FC<Props> = ({ style, onChange, value }, ref): React.ReactElement => {
//设置今天以后的日期不可选
  const disabledStartDate = useCallback(current => {
    return current && current > moment().endOf('day');
  }, []);
  return (
    <>
      <RangePicker
        style={style}
        showTime={{ format: 'YYYY-MM-DD HH:mm:ss' }}
        format="YYYY-MM-DD HH:mm:ss"
        placeholder={['起始时间', '结束时间']}
        disabledDate={disabledStartDate}
        onChange={onChange}
        ref={ref}
        value={value}
      />
    </>
  );
};
export default forwardRef(TimePicker);

2. 使用方法

(1)单独使用

通过onChange函数就可以拿到数据

<col span={6}>
   <label>时间选取:</label>
   <TimePicker  onChange={handleOnchange} />
</col>

(2)在form表单中使用

这里不使用onChange函数,可以使用form的onSubmit/onFinish函数就可以拿到起始时间和结束时间

  <Form.Item label={'时间范围'}  >
     {getFieldDecorator(`group`, {
       initialValue: group,//默认数据
     })(
      <TimePicker/>
     )}
   </Form.Item>

三、form表单封装

1. 代码

(1)html

import React, { FC, useCallback, useRef, ReactNode } from 'react';
import { ActionItem } from './form-types';
import { Input, Select, Button } from 'antd';
import Form, { FormComponentProps } from 'antd/lib/form';
import TimePicker from 'components/time-picker';
import TreePicker from 'components/tree-picker';
import styles from './form-basic.module.scss';

const { Option } = Select;

interface ComponentProps {
  actions: ActionItem[];
  onSubmit(value?: ActionItem['value']): void;
  render?(value?: ReactNode): void;
  method?: string;
}
type Props = FormComponentProps & ComponentProps;
const FormCustom: FC<Props> = ({ actions, method, form, children, onSubmit, render }): JSX.Element => {
  const { getFieldDecorator, setFieldsValue } = form;
  const TimePickerRef = useRef(null);
  const TreePickerRef = useRef(null);
  const renderForm = useCallback(
    (item, index) => {
      switch (item.type) {
        case 'input':
          return (
            <Form.Item key={index} label={item.label}>
              {getFieldDecorator(`${item.name}`, {
                initialValue: item.value || '',
                rules: [{ required: item.required, message: `请输入${item.label}` }],
              })(<Input placeholder={item.placeholder || `请输入${item.label}`} style={item.style} />)}
            </Form.Item>
          );
        case 'select':
          return (
            <Form.Item label={item.label} key={index}>
              {getFieldDecorator(`${item.name}`, {
                initialValue: item.value || '',
                rules: [{ required: item.required, message: `请选择${item.label}` }],
              })(
                <Select style={item.style} placeholder={item.placeholder || `请选择${item.label}`}>
                  {item.options.map((o: any, i: any) => {
                    return (
                      <Option key={i} value={o.value}>
                        {o.label}
                      </Option>
                    );
                  })}
                </Select>,
              )}
            </Form.Item>
          );
        case 'timePicker':
          return (
            <Form.Item label={item.label} key={index}>
              {getFieldDecorator(`${item.name}`, {
                initialValue: item.value,
              })(<TimePicker style={item.style} ref={TimePickerRef} />)}
            </Form.Item>
          );
        case 'treePicker':
          return (
            <Form.Item label={item.label} key={index} className={styles['tree-basic']}>
              {getFieldDecorator(`${item.name}`, {
                initialValue: item.value,
              })(
                <TreePicker
                  style={item.style}
                  ref={TreePickerRef}
                  treeData={item.treeOptions}
                  placeholder={item.placeholder || `请选择${item.label}`}
                />,
              )}
            </Form.Item>
          );
      }
    },
    [getFieldDecorator],
  );
  const handleSubmit: React.FormEventHandler<HTMLFormElement> = useCallback(
    (e): void => {
      e.preventDefault();
      form.validateFields((err, values) => {
        if (!err) {
          onSubmit(values);
        }
      });
    },
    [form, onSubmit],
  );
  return (
    <div>
      <Form layout="inline" onSubmit={handleSubmit}>
        {actions.map((item: ActionItem, index: number) => {
          return renderForm(item, index);
        })}
        {children}
        <Form.Item>
          <Button type="primary" htmlType="submit">
            {method ? method : '搜索'}
          </Button>
        </Form.Item>
        {render && render()}
      </Form>
    </div>
  );
};
export default FormCustom;

(2)数据结构

import { MarketList } from 'utils/convertToTreeData';//前面已经给了
import moment from 'moment';
type ValueForm = string | null | undefined | moment.Moment;
export interface ActionItem {
  type: string;
  name: string;
  label: string;
  value: ValueForm | ValueForm[];
  min?: number;
  max?: number;
  required?: boolean;
  placeholder?: string;
  options?: ActionOptionsItem[];
  treeOptions?: MarketList[];
  style?: {};
}
export interface ActionOptionsItem {
  value: string;
  label: string;
}

(3)css

使用的css较少

.tree-basic {
  margin-top: 4px;
}

2. 使用方法

(1)导入该组件

<FormCustom
  onSubmit={handleSubmit} //提交表单的方法
   form={form}
   actions={formData} //显示该表单的遍历数据
   render={(): ReactNode => {
     return (
     //在这里可以在搜索的button后面增加其他的button,不会影响美观【跑到搜索button前面】
       <Form.Item>
         <Button type="primary" style={{ marginLeft: 8 }} onClick={handleImportBtnClick}>
           我是其他的button
         </Button>
       </Form.Item>
     );
   }}
 >
 //在这里可以增加其他表单
   <Form.Item label={'我是其他表单'}>
     {getFieldDecorator('xxx', {
       rules: [{ required: true, message: '请选择' }],
     })(
       <Select style={{ width: 130 }} placeholder={'请选择'} onChange={handleBizCodeChange}>
         {groups.map(
           (item): JSX.Element => {
             return (
               <Option value={item.value} key={item.key}>
                 {item.name}
               </Option>
             );
           },
         )}
       </Select>,
     )}
   </Form.Item>
   
 </FormCustom>

(2)表单提交方法

a. 使用了时间选择框

需要将时间选择不以数据形式展示的,需要拿出来。【对应于上面,如果时间选择框在form.item中使用一样】

  const handleSubmit = (values: any): void => {
    const {
      timeRange: [from, to],
      ...rest
    } = values;

    console.log({
      ...rest,
      from: from ? moment(from._d).format('YYYY-MM-DD HH:mm:ss') : '',
      to: to ? moment(to._d).format('YYYY-MM-DD HH:mm:ss') : '',
    });
  };
b. 没有使用时间选择框

直接可以拿到数据

  const handleSubmit = (values: any): void => {
    console.log(values)
  };

(3)formData数据


//ActionItem上面有定义
  const formData: ActionItem[] = [
    {
      type: 'input',
      label: '姓名',
      value: name || '',
      name: 'name',
    },
    {
      type: 'select',
      label: '下拉框',
      value: limitStatus || 'permit',
      name: 'limitStatus',
      options: STATUS_TYPES,
      style: { width: '80px' },
    },
    {
      type: 'timePicker',
      label: '时间选择框',
      value: [timeJudge(from as string), timeJudge(to as string)],
      name: 'timeRange',
    },
    {
      type: 'treePicker',
      label: '树形选择框',
      //判断数据是否存在,如果转成字符串,则需要转会来
      //value: (limitType as string) ? (limitType as string).split(',') : [],
      //没有转成字符串则不需要,只需要判断是否为空就行
      value: limitType || [],
      name: 'limitType',
      treeOptions: LIMIT_TYPE,
      style: { width: '200px' },
    },
  ];
  //下拉框数据格式
    const STATUS_TYPES = [
    {
      value: '',
      label: '全部',
    },
    {
      value: '111',
      label: '111',
    },
    {
      value: '222',
      label: '222',
    },
  ];

(4)其他

有timeJudge函数,是我自己写的,用来判断初始值是否为空字符串

import moment from 'moment';

export const timeJudge = (value: string): any => {
  if (value && value.length > 0) {
    return moment(value);
  } else {
    return undefined;
  }
};

综上,完成form表单封装,实践中就可以用了

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值