背景
在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
都能获取到上次修改后的列数组