Problem Description
In project development, I encountered such a requirement: the data in the table needs to be dragged and sorted.
The effect diagram is as follows:
train of thought
Install two plugins:
- react-sortable-hoc (或者 react-beautiful-dnd)
- array-move
npm install --save react-sortable-hoc
npm install --save array-move
analyze
1. react-sortable-hoc
react-sortable-hoc
Is a set of react
higher-order components (parameters or return values are functions), used to implement the drag sorting function, which can convert any list into an animated, accessible and touch-friendly sortable list. It can be integrated with existing components, supports functions such as dragging handles, automatic scrolling, locking axes, and operating events, and has process animation effects. Can be dragged horizontally and vertically .
Use of react-sortable-hoc:
react-sortable-hoc
Two particularly important APIs are provided
- SortableContainer : is the container for all draggable sortable elements
- SortableElement : is the container for each element to be dragged and sorted
- SortableHandle : is the container that defines the drag handle
import {
SortableHandle } from 'react-sortable-hoc';
import {
MenuOutlined } from '@ant-design/icons';
const DragHandle = SortableHandle(() => <MenuOutlined style={
{
cursor: 'grab', color: '#999' }} />)
{
title: '拖动排序',
dataIndex: 'sort',
width: 120,
align: 'center',
className: 'drag-visible',
editable: false,
render: () =>{
if (editable) return <DragHandle />;
return <span>禁止拖动</span>
},
},
SortableHandle refers to the arrow part below
SortableElement
A index
property to sort each element to be dragged
SortableContainer
Provide a method onSortEnd
that can deconstruct two formal parameters: { oldIndex , newIndex }
, one is the mark of the dragged element, and the other is the mark of the place to be dropped.
Finally, use to arrayMoveImmutable
swap the position of the array.
axis indicates the dragging direction, x is horizontal dragging, y is vertical dragging, the default is vertical dragging
2. array-move
array-move
In fact, it is an API, and its main function is to exchange the position of elements in the array.
See the example below:
// 在tsx文件中
import React, {
useEffect } from 'react';
import {
arrayMoveImmutable } from 'array-move';
const Index = () => {
useEffect(() => {
let arr = ['a', 'b', 'c']
let result = arrayMoveImmutable(arr, 1 , 2)
console.log(result)
// 结果输入为: [ 'a', 'c', 'b' ]
})
}
export default Index
use
import {
SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import {
arrayMoveImmutable } from 'array-move';
// 定义拖拽的table 容器
const DragTableContainer = SortableContainer((props) => <tbody {
...props}>)
// 定义 拖拽的 行
const DragTableItem = SortableElement((props) => <tr {
...props}>)
// 定义拖拽手柄
const DragHandle = SortableHandle(() => (
<MenuOutlined title='拖拽排序' />
))
// 表格排序方法
const onSortEnd = ({
oldIndex, newIndex}: {
oldIndex: number; newIndex: number }) => {
if (oldIndex !== newIndex) {
const newData: any[] = arrayMoveImmutable(([] as any[]).concat(dataSource), oldIndex, newIndex).filter((el: any) => !!el);
handleAllSave(newData) // 父组件传过来的方法,用于更新表格第一列的序号
}
}
// 所有可拖拽排序元素的容器
// DragTableContainer 是上面通过 SortableContainer 定义的拖拽的table 容器
// useDragHandle 参数,意思是: 使用行把手拖拽行排序
// disableAutoscroll 参数,禁止自动滚动
// helperClass 参数,可修改拖拽样式
// onSortEnd `SortableContainer` 提供的一个方法,这个方法可以解构两个形参:`{ oldIndex , newIndex }`,一个是拖拽元素的标记,一个是将要放的地方的标记,用于表格拖拽排序
const DraggableContainer = (props: any) => <DragTableContainer useDragHandle disableAutoscroll helperClass="row-dragging" onSortEnd={
onSortEnd} {
...props}/>
// 定义 拖拽的 行
// DraggableBodyRow 返回的是由 SortableItem 包裹的每一行元素
const DraggableBodyRow = ({
className, style, ...restProps}: any) => {
const index = dataSource.findIndex((x: any) => x.orderNum === restProps['data-row-key']);
return (<SortableItem index={
index} {
...restProps} />)
}
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// 封装的子组件
const EditableTable = (props: any) => {
let {
title = '', subtitle = '', columns, rowClassName = () => 'editable-row', dataSource, handleSave, handleAllSave, rowKey, placeholder, clickRow, loading = false, scroll } = props;
const styles = {
tabletitle: {
fontWeight: 800, color: '#0095ff', fontSize: '16px' },
subtitle: {
color: '#000000', fontSize: '12px' },
};
columns = columns.map((col: any) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: any) => ({
record,
isRowDisable: col.isRowDisable,
isNumber: col.isNumber,
editable: col.editable,
editdisable: col.editdisable,
dataIndex: col.dataIndex,
title: col.title,
handleSave: handleSave,
formRules: col.rules,
placeholder: col?.placeholder,
precision: col?.precision,
min: col?.min,
step: col?.step,
max: col?.max,
formatter: col?.formatter,
parser: col?.parser,
}),
};
});
/**
* 表格行属性
* @param record 表格每行的数据
* @returns
*/
const onRow = (record: any) => {
return {
onClick: clickRow ? () => clickRow(record) : undefined,
}
}
const onSortEnd = ({
oldIndex, newIndex}: {
oldIndex: number; newIndex: number }) => {
if (oldIndex !== newIndex) {
const newData: any[] = arrayMoveImmutable(([] as any[]).concat(dataSource), oldIndex, newIndex).filter((el: any) => !!el);
handleAllSave(newData)
}
}
const DraggableContainer = (props: any) => <SortableBody useDragHandle disableAutoscroll helperClass="row-dragging" onSortEnd={
onSortEnd} {
...props}/>
const DraggableBodyRow = ({
className, style, ...restProps}: any) => {
const index = dataSource.findIndex((x: any) => x.orderNum === restProps['data-row-key']);
return (<SortableItem index={
index} {
...restProps} />)
}
return (
<Fragment>
<div style={
{
display: 'flex', marginBottom: '6px' }}>
<Table
className="wrap"
style={
{
width: '100%' }}
locale={
{
emptyText: '暂无数据' }}
components={
{
body: {
wrapper: DraggableContainer,
row: DraggableBodyRow,
// cell: EditableCell
}
}}
rowClassName={
rowClassName}
bordered
dataSource={
dataSource}
columns={
columns}
pagination={
false}
rowKey='orderNum'
scroll={
scroll || {
y: 500 }}
onRow={
onRow}
loading={
loading}
/>
</div>
</Fragment>
);
};
export default memo(EditableTable);