使用react-resizable实现antd的table表格列拖拽调整宽度以及解决闭包

背景

在antd的3.x版本是有可伸缩列功能的,但是到了4.x以后,antd的官网就没有示例了,但是实现的方式还是一样的,本案例基于3.x的案例,改为拖拽结束的时候更新列的位置,而不是实时更新拖拽位置。
下面是实现方式

1 安装react-resizable
 yarn add react-resizable
2 源码

ResizeableTitle.jsx

import React, { useState, useEffect, useRef, useImperativeHandle, useCallback } from 'react';
import { Resizable } from 'react-resizable';

export default (props) => {
  const { onResize, width, datagridRef, stretchRef, ...restProps } = props;
  const [offset, setOffset] = useState(0);
  if (!width) {
    return <th {...restProps} />;
  }
  // 获取要减去的父元素距离左侧的宽度
  const getPosition = (element) => {
    let actualLeft = element.offsetLeft;
    let current = element.offsetParent;
    while (current !== null) {
      //当它上面有元素时就继续执行
      actualLeft += current.offsetLeft;
      current = current.offsetParent; //继续找父元素
    }
    return { x: actualLeft };
  };

  const handleResize = (e, { size }) => {
    // 这里只更新偏移量,数据列表其实并没有伸缩
    setOffset(size.width - width);
    let parentNodeX = getPosition(datagridRef.current).x;
    let stretchDom = stretchRef.current;
    stretchDom.style.display = 'block';
    stretchDom.style.left = `${e.clientX - parentNodeX + 2}px`;
  };
  const handleResizeStop = (...arg) => {
    // 拖拽结束以后偏移量归零
    setOffset(0);
    let stretchDom = stretchRef.current;
    stretchDom.style.display = 'none';
    stretchDom.style.left = `0px`;
    // props传进来的事件,在外部是列数据中的onHeaderCell方法提供的事件
    onResize(...arg);
  };
  const _ResizableSpan = (
    <div
      className="react-resizable-handle"
      // 拖拽层偏移
      style={{ transform: `translateX(${offset}px)` }}
      onClick={(e) => {
        // 取消冒泡,不取消貌似容易触发排序事件
        e.stopPropagation();
        e.preventDefault();
      }}
    />
  );
  return (
    <Resizable
      width={width + offset}
      handle={_ResizableSpan}
      height={0}
      // 拖拽事件实时更新
      onResize={handleResize}
      // 拖拽结束更新
      onResizeStop={handleResizeStop}
      draggableOpts={{ enableUserSelectHack: false }}
    >
      <th {...restProps} />
    </Resizable>
  );
};

index.jsx

import React, { useState, useRef, useEffect } from "react";
import { Table } from "antd";
import ResizeableTitle from './ResizeableTitle';
import styles from './index.less';

let dataSource = [];
for (let i = 1; i <= 100; i++) {
  dataSource.push({
    key: i,
    number: i,
    text: `单行${i}`,
    textarea: `多行${i}`,
  });
}

export default  () => {
  const [cols, setCols] = useState([
    {
      title: "数值输入框",
      dataIndex: "number",
      key: "number",
      width: 300,
      ellipsis: true,
    },
    {
      title: "单行文本框",
      dataIndex: "text",
      key: "text",
      width: 300,
      ellipsis: true,
    },
    {
      title: "多行文本框",
      dataIndex: "textarea",
      key: "textarea",
      ellipsis: true,
    },
  ]);
  const [columns, setColumns] = useState([]);

  const datagridRef = useRef(null);
  const stretchRef = useRef(null);

  useEffect(() => {
    setColumns(
      (cols || []).map((col, index) => ({
        ...col,
        onHeaderCell: (column) => ({
          width: column.width,
          stretchRef,
          datagridRef,
          onResize: handleResize(index),
        }),
      }))
    );
  }, [cols]); 

  // 定义头部组件
  const components = {
    header: {
      cell: ResizeableTitle,
    },
  };

  // 处理拖拽
  const handleResize =
    (index) =>
    (e, { size }) => {
      const nextColumns = [...cols];
      // 拖拽是调整宽度
      nextColumns[index] = {
        ...nextColumns[index],
        width: size.width,
      };
      setCols([...nextColumns]);
    };
  return (
    <div className={styles.resizeTable} ref={datagridRef}>
      <Table
        dataSource={dataSource}
        rowKey={(record) => record.key}
        columns={columns}
        components={components}
      />
      {/* 拖拽列的线样式 */}
      <div className={styles.styledLine} ref={stretchRef} style={{ bottom: 64 }} />
    </div>
  );
};

index.less

.resizeTable {
  position: relative;
  background-color: #fff;
  :global {
    .ant-table-thead {
      th {
        -moz-user-select: none;
        -o-user-select: none;
        -khtml-user-select: none;
        -webkit-user-select: none;
        -ms-user-select: none;
        user-select: none;
      }
    }

    .ant-table-cell {
      .superform-field-control-readonly {
        display: block;
        overflow: hidden;
        line-height: 32px;
        white-space: nowrap;
        text-overflow: ellipsis;
        word-break: keep-all;
      }
    }
    .ant-table-thead,
    .ant-table-tbody {
      tr {
        td,
        th {
          padding: 8px;
        }
      }
    }
    .react-resizable {
      position: relative;
      background-clip: padding-box;
      &:hover {
        // background-color: #e4e4e8;
        background-color: #f5f5f5;
      }
    }

    .react-resizable-handle {
      position: absolute;
      right: 0px;
      bottom: 0;
      z-index: 1;
      width: 2px;
      height: 100%;
      cursor: col-resize;
      &:hover {
        background-color: #00bba8;
      }
      &:active {
        background-color: transparent;
      }
    }
  }
}
.styledLine {
  position: absolute;
  top: 0px;
  left: 0px;
  z-index: 999;
  display: none;
  width: 2px;
  height: 100%;
  background-color: #00bba8;
}

3 闭包问题及解决方案

上述代码在第一次拖拽的时候效果是正常的,但是在第二次拖拽的时候,handleResize里const nextColumns = [...cols];时,获取到的cols是每一列是没有onHeaderCell函数的,导致无法继续拖拽。
解决方案

import React, { useState, useRef, useEffect } from 'react';
import { Table } from 'antd';
import ResizeableTitle from './ResizeableTitle';
import styles from './index.less';

let dataSource = [];
for (let i = 1; i <= 100; i++) {
  dataSource.push({
    key: i,
    number: i,
    text: `单行${i}`,
    textarea: `多行${i}`,
  });
}

export default () => {
  const [cols, setCols] = useState([
    {
      title: '数值输入框',
      dataIndex: 'number',
      key: 'number',
      width: 300,
      ellipsis: true,
    },
    {
      title: '单行文本框',
      dataIndex: 'text',
      key: 'text',
      width: 300,
      ellipsis: true,
    },
    {
      title: '多行文本框',
      dataIndex: 'textarea',
      key: 'textarea',
      ellipsis: true,
    },
  ]);
  const tableColsRef = useRef();
  const [columns, setColumns] = useState([]);

  const datagridRef = useRef(null);
  const stretchRef = useRef(null);

  useEffect(() => {
    setColumns(
      (cols || []).map((col, index) => ({
        ...col,
        onHeaderCell: (column) => ({
          width: column.width,
          stretchRef,
          datagridRef,
          onResize: handleResize(index),
        }),
      })),
    );
    tableColsRef.current = (cols || []).map((col, index) => ({
      ...col,
      onHeaderCell: (column) => ({
        width: column.width,
        stretchRef,
        datagridRef,
        onResize: handleResize(index),
      }),
    }));
  }, [cols]);

  // 定义头部组件
  const components = {
    header: {
      cell: ResizeableTitle,
    },
  };

  // 处理拖拽
  const handleResize =
    (index) =>
    (e, { size }) => {
      const nextColumns = [...tableColsRef.current];
      // 拖拽是调整宽度
      nextColumns[index] = {
        ...nextColumns[index],
        width: size.width,
      };
      setCols([...nextColumns]);
      tableColsRef.current = [...nextColumns];
    };
  return (
      <div className={styles.resizeTable} ref={datagridRef}>
      <Table
        dataSource={dataSource}
        rowKey={(record) => record.key}
        columns={columns}
        components={components}
      />
      {/* 拖拽列的线样式 */}
      <div className={styles.styledLine} ref={stretchRef} style={{ bottom: 64 }} />
    </div>
  );
};

添加tableColsRef用于存储修改后的cols数组,这样每次触发handleResize都能获取到上次修改后的列数组

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值