react 搜索组件

356 阅读4分钟

主要实现的功能

  1. ​ 能够实时搜索,能够手动搜索、清除
  2. ​ 能根据不同类型显示各种表单类型,例如:日期、文本框、下拉列表
  3. ​ 每个搜索选项,可以根据需要设置单选或多选操作

实现过程

构建组件结构

​ 基于ant design 的 Form表单进行开发,主要分为两部分,表单搜索选项和表单搜索、清除按钮,

​ 表单搜索选项,是一些常见的表单选项,如果是一些不常见表单类型,采用自定义表单组件

​ 实时搜索,主要调用了Form表单的 onValuesChange 方法

配置项:

属性 类型 描述 是否必填 默认值
formInitialValue Array 初始值 可选
filterConfig Array 搜索项字段配置 必填
buttonLocation String 按钮位置 可选 left
okButtonStyle String 搜索按钮样式 可选
resetButtonStyle String 清除按钮样式 可选
onSubmit Function 搜索事件 可选
onReset Function 清除事件 可选

filterConfig

属性 类型 描述 是否必填 默认值
label String 字段 标签
name String 字段 name
type String 搜索类型(多选按钮或者单选按钮: Button(默认)、日期: Date、下拉选择: Select、文本框: Text) Button
options Array type为 Button、Select 时

添加反向数据流

​ Form 会对直接子元素绑定表单功能,即不用去手动绑定,就能获取其数据,但是对于一些自定义表单组件,就要提供 onChange 事件或 trigger 的值同名的事件,在把自定义表单组件的数据传到父组件中去

代码结构

SearchFilter.tsx

/**
 * 搜索组件
 * <SearchFilter
    filterConfig={this.filterConfig}
    onSubmit={this.handleSearch}
    onReset={this.handleReset}
  />
  表单配置项
  filterConfig = [
    {
      label: '备件类型',
      name: 'type',
      type: 'Button',   // 表单类型 多选按钮或单选按钮:Button(默认)、 日期:Date、 下拉选择:Select、 文本框:Text
      mode: 'multiple', // 默认多选    单选为 single
      options: [
        {
          id: '',
          label: ''
        }
      ]
    },
    {
      label: '时间周期',
      name: 'date_picker',
      type: 'Date'
    },
  ];
 */
import React, { Component } from 'react';
import { Button, Form, Row, Col, Select, Card, Input } from 'antd';
import { FormComponentProps } from 'antd/lib/form/Form';
import ChoiceDatePicker from '../ChoiceDatePicker';
import ButtonSelect from '../ButtonSelect';
import {
  filterForm, filterFormItemWithNormal,
} from './index.less';

const { Option } = Select;

export interface ItemProps {
  id: string | number;
  label: string | number;
}

export interface SearchFilterValue {
  [propName: string]: string | number | any;
}

export interface FormItem {
  label: string;
  mode: string;
  name: string;
  type: string;
  options: ItemProps[];
}

export interface SearchFilterProps extends FormComponentProps{
  onSubmit?: (values: SearchFilterValue) => void;
  onReset?: () => void;
  formInitialValue?: {
    [propName: string]: string | number;
  };
  disableFields?: string[];
  filterConfig: FormItem[];
  okButtonStyle?: {
    [propName: string]: string | number;
  };
  resetButtonStyle?: {
    [propName: string]: string | number;
  };
  buttonLocation?: 'left' | 'right';
}

export interface SearchFilterState {
}

class SearchFilter extends Component<SearchFilterProps, SearchFilterState> {

  handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    this.props.form.validateFields((err: any, values: SearchFilterValue) => {
      const { onSubmit } = this.props;
      const formData = {};
      for (let key in values) {
        if (values[key] !== undefined) {
          if (Array.isArray(values[key])) {
            if (values[key].length > 0)
              formData[key] = values[key].join(',');
          } else {
            formData[key] = values[key];
          }
        }
      }
      if (onSubmit) onSubmit(formData);
    });
  };

  handleReset = () => {
    this.props.form.resetFields();
    const { onReset } = this.props;
    if (onReset) onReset();
  };

  isDisabled = (name: string) => {
    const { disableFields } = this.props;
    return disableFields && disableFields.length>0 && disableFields.includes(name)
  };

  render() {
    const {
      form: {getFieldDecorator},
      formInitialValue,
      filterConfig,
      okButtonStyle,
      resetButtonStyle,
      buttonLocation,
    } = this.props;
    return (
      <>
        <Card bordered={false} style={{ marginTop: 24 }}>
          <Form className={filterForm} onSubmit={this.handleSearch} labelAlign={"right"} labelCol={{xxl: {span: 1}, xl: {span: 2}, lg: {span: 3}, sm: {span: 3}}} wrapperCol={{span: 20}} style={{textAlign: 'left', marginLeft: -10}}>
            {
              filterConfig && filterConfig.map(item => {
                switch(item.type) {
                  case 'Select':
                    return <Form.Item label={item.label} key={item.name} className={filterFormItemWithNormal}>
                      {getFieldDecorator(item.name, {
                        initialValue: formInitialValue ? formInitialValue[item.name] : undefined
                      })(
                        <Select
                          showSearch
                          mode={item.mode}
                          style={{ width: '100%' }}
                          placeholder="请选择"
                          disabled={ this.isDisabled(item.name) }
                        >
                          {item.options && item.options.length > 0 ? item.options.map((valueItem: ItemProps) =>
                            (valueItem.label && <Option key={valueItem.id}>{valueItem.label}</Option>)
                          ) : undefined}
                        </Select>
                      )}
                    </Form.Item>
                    break;
                  case 'Text':
                    return <Form.Item label={item.label} key={item.name} className={filterFormItemWithNormal}>
                      {getFieldDecorator(item.name, {
                        initialValue: formInitialValue ? formInitialValue[item.name] : undefined
                      })(
                        <Input
                          allowClear
                          disabled={ this.isDisabled(item.name) }
                        />
                      )}
                    </Form.Item>
                    break;
                  case 'Date':
                    return <Form.Item label={item.label} key={item.name} className={filterFormItemWithNormal}>
                      {getFieldDecorator(item.name, {
                        initialValue: formInitialValue ? formInitialValue[item.name] : undefined
                      })(
                        <ChoiceDatePicker
                          choiceList={['oneMonth', 'halfYear', 'oneYear']}
                        />
                      )}
                    </Form.Item>
                    break;
                  default:
                    // Button
                    return <Form.Item label={item.label} key={item.name} className={filterFormItemWithNormal}>
                      {getFieldDecorator(item.name, {
                        initialValue: formInitialValue ? formInitialValue[item.name] : undefined
                      })(
                        <ButtonSelect
                          formItem={item}
                        />
                      )}
                    </Form.Item>
                    break;
                }
              })
            }
            <Row>
              <Col
                span={24}
                style={ buttonLocation && buttonLocation === 'right' ? {
                  textAlign: 'right',
                } : { textAlign: 'left', marginLeft: 10 }}
              >
                <Button type="primary" htmlType="submit" style={okButtonStyle}>
                  搜索
                </Button>
                <Button
                  style={Object.assign({
                    marginLeft: 8,
                  }, resetButtonStyle)}
                  onClick={this.handleReset}
                >
                  清除
                </Button>
              </Col>
            </Row>
          </Form>
        </Card>
      </>
    );
  }
}

let timeout: any;
const onValuesChange = (props: SearchFilterProps, changedValues: any, allValues: any) => {
  const formData = {};
  for (let key in allValues) {
    if (allValues[key] !== undefined) {
      formData[key] = allValues[key];
      if (Array.isArray(allValues[key])) {
        if (allValues[key].length > 0)
          formData[key] = allValues[key].join(',');
      } else {
        formData[key] = allValues[key];
      }
    }
  }
  if (props.onSubmit) {
    if (timeout) {
      clearTimeout(timeout);
      timeout = null;
    }
    var debounceChange = function() {
      if (props.onSubmit)
        props.onSubmit(formData)
    };
    timeout = setTimeout(debounceChange, 500);
  }
};

export default Form.create<SearchFilterProps>({ name: 'part_filter', onValuesChange: onValuesChange })(SearchFilter);

ButtonSelect.tsx

import React, { useState, useEffect } from 'react';
import { Button } from 'antd';
import {
  filterFormItemButton, filterFormItemButtonSelected,
} from './index.less';

export interface ItemProps {
  id: string | number;
  label: string | number;
}

export interface FormItem {
  label: string;
  mode: string;
  name: string;
  type: string;
  options: ItemProps[];
}

interface ButtonSelectProps {
  value?: any[] | undefined;
  onChange?: (values: any) => void;
  formItem: FormItem;
}

// 多选按钮 单选按钮
const ButtonSelect = (props: ButtonSelectProps) => {
  const { formItem, onChange, value } = props;
  const [selected, setSelected] = useState(value ? value : []);

  useEffect(() => {
    const val = props.value ? props.value : [];
    setSelected(val)
}, [props.value])

  const triggerChange = (changedValue: any[]) => {
    if (onChange) {
      onChange(changedValue);
    }
  };

  const handleChange = (item: FormItem, value: string | number) => {
    let valueList: any[] = [value];
    if (selected && selected.includes(value)) {
      valueList = selected.filter(v => v !== value);
    } else if (selected) {
      if (item.mode === 'single') {
        valueList = [value];
      } else {
        valueList = [...selected, value];
      }
    }
    setSelected(valueList);
    triggerChange(valueList);
  };

  return (
    <>
      {formItem.options && formItem.options.map((valueItem: ItemProps) =>
        <Button
          key={valueItem.id}
          size='small'
          type={selected && selected.includes(valueItem.id) ? undefined : 'link'}
          className={selected && selected.includes(valueItem.id) ? filterFormItemButtonSelected : filterFormItemButton}
          onClick={()=>{handleChange(formItem, valueItem.id)}}
        >
          {valueItem.label}
        </Button>
      )}
    </>
  );
};

export default ButtonSelect;

性能优化

​ 因为是实时搜索,特别是文本类型的搜索,每次输入,都会触发请求,为了节省资源,添加了防抖操作。

不足之处

​ 当搜索项为下拉列表时,如果选项过多,会造成页面卡顿问题,正在解决中.....