大家好,我是张三岁🤣,一只法系前端⚖️。爱分享🖋️、爱冰冰🧊🧊。
欢迎小伙伴们加我微信:maomaoibingbing,拉你进群,一起讨论,期待与大家共同成长🥂。
前言
最近在项目中遇到了一个需求,客户希望表格的列宽可以拖动伸缩。该项目的技术栈是 React
+ Ant Design
。后来看了文档后进行了简单的改动和封装。以下将使用函数式组件,希望能对各位有所帮助,蟹蟹٩('ω')و。
一、业务场景
客户希望表格的标题栏可以拖动来改变整列的宽窄,为了 向别人装x 更方便的查看数据多的列,并增加表格组件的交互效果。
二、实现思路
Ant Design
文档中已经明确给出了类式组件版本的 Demo ,此处在这个示例的基础上进行修改,改为函数式组件,并对其进行封装,提高其可复用性。此处奉上 官方文档地址传送门 。
三、进行编码
1. 安装依赖
首先我们需要安装依赖 react-resizable
,此依赖是实现该拖拽伸缩功能的核心。
# npm 安装
npm install react-resizable --save
# yarn 安装
yarn add react-resizable
2. 重写案例
接下来我们将示例的类式写法改为函数式。
// 列表页面
import { useState } from 'react';
import { Table } from 'antd';
import { Resizable } from 'react-resizable';// 核心依赖
import './resizable-title.css';// 此种引入方式不会在类名中添加哈希值,故仅作为替换样式时使用
const ResizableTitle = ({ onResize, width, ...restProps }) => {
if (!width) { return (<th {...restProps} />) };
return (
<Resizable
width={width}
height={0}
handle={
<span
className="react-resizable-handle"
onClick={e => { e.stopPropagation() }}
/>
}
onResize={onResize}
draggableOpts={{ enableUserSelectHack: false }}
>
{/* 此处增加行内样式目的:让标题的文字不可选中 */}
<th {...restProps} style={{ ...restProps?.style, userSelect: 'none' }} />
</Resizable>
);
};
const List = () => {
// 表格数据
const data = [
{
key: 0,
date: '2018-02-11',
amount: 120,
type: 'income',
note: 'transfer',
},
{
key: 1,
date: '2018-03-11',
amount: 243,
type: 'income',
note: 'transfer',
},
{
key: 2,
date: '2018-04-11',
amount: 98,
type: 'income',
note: 'transfer',
},
];
// 列配置
const columns = [
{
title: 'Date',
dataIndex: 'date',
width: 200,
},
{
title: 'Amount',
dataIndex: 'amount',
width: 100,
sorter: (a, b) => a.amount - b.amount,
},
{
title: 'Type',
dataIndex: 'type',
width: 100,
},
{
title: 'Note',
dataIndex: 'note',
width: 100,
},
{
title: 'Action',
key: 'action',
render: () => <a>Delete</a>,
}
];
// 用 useState 创建响应式数据
const [cols, setCols] = useState(columns);
const colsArray = cols.map((col, index) => {
return {
...col,
onHeaderCell: column => ({ width: column.width, onResize: handleResize(index) })
};
});
// todo 调整列宽
const handleResize = index => {
return (_, { size }) => {
const temp = [...cols];
temp[index] = { ...temp[index], width: size.width };
setCols(temp);
};
};
return (
<div>
<Table
components={{ header: { cell: ResizableTitle } }}
columns={colsArray}
dataSource={data}
pagination={false}
/>
</div>
);
};
export default List;
此处直接使用普通的 CSS
,单独将控制伸缩列的样式放到一个文件中,避免与其他样式代码冲突。页面中其他样式代码可以单独放入同目录下的 index.less
,并用 import styles from './index.less'
方式引入。
/* resizable-title.css */
/* 列表页面 */
.react-resizable-handle {
position: absolute;
right: -5px;
bottom: 0;
z-index: 1;
width: 10px;
height: 100%;
cursor: col-resize;
}
效果似乎还不错,但是我们接下来还要对其进行封装。
3. 封装复用
为了提高复用性,我们需要把代码封装成一个公共组件。内部使用 Ant Design
的表格 Table
组件。
// components/ResizableTable/index.jsx
// 可拖拽表格组件
import { useState } from 'react';
import { Table } from 'antd';
import { Resizable } from 'react-resizable';
import './index.css';
const ResizableTitle = ({ onResize, width, ...restProps }) => {
if (!width) { return (<th {...restProps} />) };
return (
<Resizable
width={width}
height={0}
handle={
<span
className="react-resizable-handle"
onClick={e => { e.stopPropagation() }}
/>
}
onResize={onResize}
draggableOpts={{ enableUserSelectHack: false }}
>
<th {...restProps} style={{ ...restProps?.style, userSelect: 'none' }} />
</Resizable>
);
};
const ResizableTable = ({ columns = [], ...props }) => {
// * 列数据
const [cols, setCols] = useState(columns);
const colsArray = cols.map((col, index) => {
return {
...col,
onHeaderCell: column => ({ width: column.width, onResize: handleResize(index) })
};
});
// todo 调整列宽
const handleResize = index => {
return (_, { size }) => {
const temp = [...cols];
temp[index] = { ...temp[index], width: size.width };
setCols(temp);
};
};
return (
<Table
components={{ header: { cell: ResizableTitle } }}
columns={colsArray}
{...props}
/>
);
};
export default ResizableTable;
// 列表页面
import ResizableTable from '@/components/ResizableTable';// 可拖拽表格组件
const List = () => {
// 表格数据
const data = [
// ...
];
// 列配置
const columns = [
// ...
];
return (
<div>
<ResizableTable
columns={columns}
dataSource={data}
pagination={false}
/>
</div>
);
};
export default List;
/* components/ResizableTable/index.css */
.react-resizable-handle {
position: absolute;
right: -5px;
bottom: 0;
z-index: 1;
width: 10px;
height: 100%;
cursor: col-resize;
}
4. 组件优化
当然,仅仅封装普通表格 Table
组件是不够的,万一想使用 ProComponents
中的超级表格组件 ProTable
呢。毕竟在后台管理系统中, ProTable
的合理使用可以节省不少开发时间。同时也是为了让该自定义组件更加完善,真正够用且好用。
// components/ResizableTable/index.jsx
// 可拖拽表格组件
import { useState } from 'react';
import { Table } from 'antd';
import ProTable from '@ant-design/pro-table';
import { Resizable } from 'react-resizable';
import './index.css';
const ResizableTitle = ({ onResize, width, ...restProps }) => {
if (!width) { return (<th {...restProps} />) };
return (
<Resizable
width={width}
height={0}
handle={
<span
className="react-resizable-handle"
onClick={e => { e.stopPropagation() }}
/>
}
onResize={onResize}
draggableOpts={{ enableUserSelectHack: false }}
>
<th {...restProps} style={{ ...restProps?.style, userSelect: 'none' }} />
</Resizable>
);
};
export const ResizableTable = ({ columns = [], ...props }) => {
// * 列数据
const [cols, setCols] = useState(columns);
const colsArray = cols.map((col, index) => {
return {
...col,
onHeaderCell: column => ({ width: column.width, onResize: handleResize(index) })
};
});
// todo 调整列宽
const handleResize = index => {
return (_, { size }) => {
const temp = [...cols];
temp[index] = { ...temp[index], width: size.width };
setCols(temp);
};
};
return (
<Table
components={{ header: { cell: ResizableTitle } }}
columns={colsArray}
{...props}
/>
);
};
export const ResizableProTable = ({ columns = [], ...props }) => {
// * 列数据
const [cols, setCols] = useState(columns);
const colsArray = cols.map((col, index) => {
return {
...col,
onHeaderCell: column => ({ width: column.width, onResize: handleResize(index) })
};
});
// todo 调整列宽
const handleResize = index => {
return (_, { size }) => {
const temp = [...cols];
temp[index] = { ...temp[index], width: size.width };
setCols(temp);
};
};
return (
<ProTable
components={{ header: { cell: ResizableTitle } }}
columns={colsArray}
{...props}
/>
);
};
// 默认暴露 普通表格
export default ResizableTable;
// 列表页面
// 可以使用"默认引入"和"模块引入"两种方式,此处使用模块引入方式
import { ResizableTable, ResizableProTable } from '@/components/ResizableTable';
const List = () => {
// 表格数据
const data = [
// ...
];
// 列配置
const columns = [
// ...
];
return (
<>
<div>普通表格 ResizableTable:</div>
<br />
<ResizableTable
columns={columns}
dataSource={data}
pagination={false}
/>
<br />
<div>超级表格 ResizableProTable:</div>
<br />
<ResizableProTable
columns={columns}
dataSource={data}
pagination={false}
/>
</>
);
};
export default List;
小结
本次我们解决了 React
中表格标题行拖动伸缩的问题,同时也进行了公共组件的封装与优化。不积跬步,无以至千里;不积小流,无以成江海。为了更好更强,我们需要不断积累问题的解决方案,才能不断进步。