React高阶组件

180 阅读2分钟

高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
封装的一个table组件的高阶组件。

import React, { Component } from 'react';
import { Checkbox } from 'antd';
import './style.less';
import {
  dataFormat,
  clickTitleSorter,
  tableSorterChange,
  getFilteredArray,
  sorterRule,
  isNaNOrNull
} from '@/utils';
import { makePy } from '@/utils/wordToPinyin';
const CheckboxGroup = Checkbox.Group;

/**
 * 高阶组件 表格
 * 使用说明:
 * 必传参数:columns
 * 1、排序判断字段:
 *    sorterType
 *    'zh': 中文 'en': 中文  'num': 中文 无: 不排序
 * 2、筛选判断字段:
 *    isFilter Boolean
 * 可选参数
 * getSelectedRows: 多选框
 */
export default WrappedComponent =>
  class TableHoc extends Component {
    constructor(props) {
      super(props);
      this.state = {
        columns: [],
        dataSource: [],
        loading: false,
        currentPage: 1,
        rowClassName: '',
        selectedArr: [],
        selectedRowKeys: [],
        expandedRowKeys: []
      };
    }

    componentDidMount() {
      const { columns, tableData = [], pageSize = 10, loading = false } = this.props;
      this.setColumns(columns, tableData, pageSize);
      this.setState({loading, dataSource: this.setDataSource(tableData, columns)})
    }

    componentWillReceiveProps(nextProps) {
      const {
        columns,
        tableData = [],
        loading = false,
        currentPage = 1,
        rowClassName,
        pageSize = 10
      } = nextProps;
      const dataSource = this.setDataSource(tableData, columns);
      // 编辑和删除不改变页码
      // 编辑
      if (currentPage === 0) {
        this.setState({ loading, dataSource });
        return;
      }
      // 删除
      if (currentPage === -1) {
        // 删除某一页(>1)的唯一一条数据,需设置页码-1
        if (
          tableData.length % pageSize === 0 &&
          this.state.currentPage - 1 > 0
        ) {
          this.setState({
            currentPage: this.state.currentPage - 1,
            loading,
            dataSource
          });
          return;
        }
        this.setState({ loading, dataSource });
        return;
      }
      if (currentPage > 0) {
        this.setState({
          loading,
          dataSource
        });
        if (currentPage !== this.props.currentPage) {
          this.setState({currentPage});
        }
        if (rowClassName !== this.props.rowClassName) {
          this.setState({rowClassName})
        }
        if (this.props.tableData.length !== tableData.length) {
          this.setColumns(columns, tableData, pageSize);
        }
      }
    }

    setColumns(columns, tableData, pageSize) {
      this.setState({
        columns: columns.map((e, k) => {
          const dataIndex = e.dataIndex;
          const align = e.align ? e.align : 'center';
          const render = e.render ? e.render : t => dataFormat(t);
          const className = e.className ? e.className : '';
          if (e.title === '序号') {
            return {
              title: e.title,
              dataIndex,
              align,
              width: e.width,
              className,
              render: (t, r, idx) =>
                dataFormat(pageSize * (this.state.currentPage - 1) + 1 + idx)
            };
          }
          // 情形1: 排序+筛选
          if (e.sorterType && e.isFilter) {
            return {
              title: <a onClick={() => this.clickTitle(k)}>{e.title}</a>,
              dataIndex,
              align,
              width: e.width,
              className,
              isCheckAll: true,
              statArr: getFilteredArray(tableData, e.dataIndex),
              checkedList: getFilteredArray(tableData, e.dataIndex),
              oldCheckedList: [],
              filterDropdownVisible: false,
              onFilterDropdownVisibleChange: visible =>
                this.onFilterDropdownVisibleChange(visible, k),
              filterDropdown: () => this.renderFilterDropdown(k),
              filterIcon: () => this.renderFilterIcon(k),
              sorter: this.setSorter(e),
              render
            };
          }
          // 情形2: 排序
          if (e.sorterType) {
            return {
              title: <a onClick={() => this.clickTitle(k)}>{e.title}</a>,
              dataIndex,
              align,
              width: e.width,
              className,
              sortOrder: e.sortOrder,
              sorter: this.setSorter(e),
              render
            };
          }
          // 情形3: 筛选
          if (e.isFilter) {
            return {
              title: e.title,
              dataIndex,
              align,
              width: e.width,
              className,
              isCheckAll: true,
              statArr: getFilteredArray(tableData, e.dataIndex),
              checkedList: getFilteredArray(tableData, e.dataIndex),
              oldCheckedList: [],
              filterDropdownVisible: false,
              onFilterDropdownVisibleChange: visible =>
                this.onFilterDropdownVisibleChange(visible, k),
              filterDropdown: () => this.renderFilterDropdown(k),
              filterIcon: () => this.renderFilterIcon(k),
              render
            };
          }
          // 情形4: 展示
          return {
            title: e.title,
            dataIndex: e.dataIndex,
            align: e.align ? e.align : 'center',
            width: e.width,
            className: e.className,
            render
          };
        })
      });
    }

    setDataSource(tableData, columns) {
      let dataSource = [];
      if (tableData.length) {
        dataSource = tableData.map((v, k) => {
          const obj = v;
          obj.key = k;
          columns.forEach(e => {
            // 中文排序需将中文转换为对应的拼音首字母
            if (e.sorterType === 'zh') {
              if (isNaNOrNull(obj[e.dataIndex]) === '-') {
                obj[`${e.dataIndex}En`] = '';
              } else if (obj[e.dataIndex].label === undefined) {
                obj[`${e.dataIndex}En`] = obj[e.dataIndex]
                  ? makePy(obj[e.dataIndex])
                  : '';
              } else {
                obj[`${e.dataIndex}En`] = obj[e.dataIndex].label
                  ? makePy(obj[e.dataIndex].label)
                  : '';
              }
            }
          });
          return obj;
        });
      }
      return dataSource;
    }

    setSorter(e) {
      switch (e.sorterType) {
        case 'en':
          return (a, b) => a[e.dataIndex].localeCompare(b[e.dataIndex]);
        case 'zh':
          return (a, b) =>
            a[`${e.dataIndex}En`].localeCompare(b[`${e.dataIndex}En`]);
        default:
          if (e.sorter) {
            return e.sorter;
          }
          return (a, b) => sorterRule(a[e.dataIndex], b[e.dataIndex]);
      }
    }

    /**
     * 点击表头排序
     * @param {number} index
     */
    clickTitle(index) {
      const { columns, dataSource } = this.state;
      this.setState({
        columns: clickTitleSorter(index, [...columns], dataSource)
      });
    }
    /**
     * 渲染过滤多选框
     */
    renderFilterDropdown(k) {
      const { columns } = this.state;
      const statArr = columns[k].statArr;
      const isCheckAll = columns[k].isCheckAll;
      const checkedList = columns[k].checkedList;
      return (
        <div className="fliter-dropdown">
          <div className="dropdown-checkbox">
            <Checkbox
              checked={isCheckAll}
              onChange={e => this.onCheckAllChange(e, k)}
            >
              全部
            </Checkbox>
            <CheckboxGroup
              options={statArr}
              value={checkedList}
              onChange={checkedList => this.onCheckedListChange(checkedList, k)}
            />
          </div>
          <div className="dropdown-btns">
            <a
              className="btn btn-confirm"
              onClick={() => this.onClickConfirm(k)}
            >
              确定
            </a>
            <a className="btn btn-cancel" onClick={() => this.onClickCancle(k)}>
              取消
            </a>
          </div>
        </div>
      );
    }
    /**
     * 点击全部回调
     * @param {event} e
     */
    onCheckAllChange(e, k) {
      const { columns } = this.state;
      const statArr = columns[k].statArr;
      columns[k].checkedList = e.target.checked ? statArr : [];
      columns[k].isCheckAll = e.target.checked;
      this.setState({ columns });
    }
    /**
     * 选中单项回调
     * @param {array} checkedList
     */
    onCheckedListChange(checkedList, k) {
      const { columns } = this.state;
      columns[k].checkedList = checkedList;
      columns[k].isCheckAll = checkedList.length === columns[k].statArr.length;
      this.setState({ columns });
    }
    /**
     * 筛选下拉框状态回调
     * @param {boolean} visible
     */
    onFilterDropdownVisibleChange(visible, index) {
      const { columns } = this.state;
      const checkedList = columns[index].checkedList;
      const oldCheckedList = columns[index].oldCheckedList;
      const statArr = columns[index].statArr;
      columns[index].filterDropdownVisible = visible;
      if (visible) {
        columns[index].oldCheckedList = checkedList;
      } else {
        // 点击其他地方,弹窗消失
        // 回归到上次点击确定的多选状态
        columns[index].checkedList = oldCheckedList;
        columns[index].isCheckAll = oldCheckedList.length === statArr.length;
      }
      this.setState({ columns });
    }
    /**
     * 过滤弹框点击确定
     * @param {number} index
     */
    onClickConfirm(index) {
      const { columns } = this.state;
      const { tableData } = this.props;
      const checkedList = columns[index].checkedList;
      const dataIndex = columns[index].dataIndex;
      if (!checkedList.length) {
        return;
      }
      const dataSource = [];
      tableData.forEach(e => {
        checkedList.forEach(v => {
          if (e[dataIndex].label === v) {
            dataSource.push(e);
          } else if (e[dataIndex] === v) {
            dataSource.push(e);
          }
        });
      });
      columns[index].filterDropdownVisible = false;
      this.setState({ columns, dataSource, currentPage: 1 });
    }
    /**
     * 过滤弹框点击取消
     * @param {number} index
     */
    onClickCancle(index) {
      const { columns } = this.state;
      const oldCheckedList = columns[index].oldCheckedList;
      const statArr = columns[index].statArr;
      columns[index].filterDropdownVisible = false;
      // 回归到上次点击确定的多选状态
      columns[index].checkedList = oldCheckedList;
      columns[index].isCheckAll = oldCheckedList.length === statArr.length;
      this.setState({ columns });
    }
    /**
     * 渲染过滤图标样式
     * @param {number} index
     */
    renderFilterIcon(index) {
      const { columns } = this.state;
      const checkedList = columns[index].checkedList;
      const statArr = columns[index].statArr;
      const isFiltered = checkedList.length !== statArr.length;
      return (
        <i
          className={
            isFiltered ? 'filter-result i-hover' : 'filter-normal i-hover'
          }
        />
      );
    }
    // 多选框
    rowSelection() {
      const { selectedRowKeys, dataSource } = this.state;
      const len =dataSource.length;
      return {
        fixed: true,
        selectedRowKeys,
        hideDefaultSelections: true,
        selections: [{
          key: 'all-data',
          text: '全选',
          onSelect: () => {
            this.setState({
              selectedRowKeys: [...Array(len).keys()],
            }, () => {
              this.props.getSelectedRows(dataSource);
            });
          },
        }, 
        {
          key: 'no-data',
          text: '取消全选',
          onSelect: () => {
            this.setState({
              selectedRowKeys: [],
            }, () => {
              this.props.getSelectedRows([]);
            });
          },
        }],
        onChange: (selectedRowKeys, selectedRows) => {
          this.setState({
            selectedRowKeys
          }, () => {
            this.props.getSelectedRows(selectedRows);
          });
        },
      };
    }
    /**
     * 点击排序上下图标回调
     * @param {object} sorter
     */
    handleTableChange(sorter) {
      const { columns } = this.state;
      this.setState({ columns: tableSorterChange(sorter, columns) });
    }
    /**
     * 页码改变回调
     * @param {number} i
     */
    onPageChange(i) {
      this.setState({
        currentPage: i
      });
    }

    /**
     * 显示记录总数
     */
    showPagination = (total) => {
      return `共${total}条记录`
    }

    /**
     * 点击行事件回调
     * @param {number} idx 
     */
    onRowClick(idx) {
      this.setState(preState => {
        if (preState.expandedRowKeys[0] === idx) {
          return {
            expandedRowKeys: [],
            rowClassName: ''
          };
        }
        return {
          expandedRowKeys: [idx],
          rowClassName: (r, i) => {
            if (i === idx) return 'noraml-row';
            if (i === idx - 1) return 'pre-row';
            return '';
          }
        };
      });
    }

    render() {
      const { loading, currentPage, expandedRowKeys } = this.state;
      const {
        pageSize = 10,
        showLength,
        getSelectedRows,
        pagination,
        expandedRowRender,
        rowKey= (r, i) => i
      } = this.props;
      const expandedMethods = expandedRowRender ? {
        onRow: (r, i) => {
          return {
            onClick: () => this.onRowClick(i)
          };
        },
        expandRowByClick: true,
        expandIconAsCell: false,
        expandedRowKeys: expandedRowKeys,
        expandedRowRender: r => this.props.expandedRowRender(r)
      } : {}
      return (
        <div className="table-hoc-wrap">
          <WrappedComponent
            rowKey={rowKey}
            {...this.state}
            rowSelection={getSelectedRows ? this.rowSelection() : null}
            pagination={
              pagination !== false
              ? (
                loading
                  ? {
                      hideOnSinglePage: true,
                      current: 1,
                      pageSize: pageSize
                    }
                  : {
                      hideOnSinglePage: true,
                      current: currentPage,
                      pageSize: pageSize,
                      onChange: page => this.onPageChange(page),
                      showTotal: this.showPagination
                    }
              ) 
              : pagination
            }
            onChange={(pagination, filters, sorter) =>
              this.handleTableChange(sorter)
            }
            {...expandedMethods}
            showLength={showLength ? true : null}
          />
        </div>
      );
    }
  };