前言
最近打算研究使用React DnD来实现不同容器的tag拖拽到一个共同的tag,如下图的左侧容器的tag拖拽到右侧容器的tag,并插入到当前拖拽到的顺序:
Hooks API
主要用到useDrag、useDrop;具体可参考文档:react-dnd.github.io/react-dnd/d…
1、useDrag
useDrag:提供了一种将组件作为拖动源连接到 DnD 系统的方法;
2、useDrop
useDrop:提供了一种将组件作为放置目标连接到 DnD 系统的方法。
代码实现
1、index.tsx
import React from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import Left from './Left';
import Right from './Right';
import './index.less';
const Index = () => {
return (
<DndProvider backend={HTML5Backend}>
<div className={'content'}>
<Left/>
<Right/>
</div>
</DndProvider>
);
};
export default Index;
2、Left.tsx
import React, { useRef, useState } from 'react';
import { Tag } from 'antd';
import { useDrag } from 'react-dnd';
const Left = () => {
// 示例数据
const dataSource = [
{
key: 1,
title: '苹果',
},
{
key: 2,
title: '梨',
},
{
key: 3,
title: '芒果',
},
{
key: 4,
title: '波罗蜜',
},
{
key: 5,
title: '荔枝',
},
{
key: 6,
title: '龙眼',
},
{
key: 7,
title: '香蕉',
},
{
key: 8,
title: '榴莲',
},
];
const TagItem = ({ tag, draggable }: any) => {
//实现拖拽
const [, drag] = useDrag({
type: 'LeftTag',
item: (): API.FormulaItem => {
// 使用时间戳来定义id值,避免移动后的数据的id值相同
return { ...tag, id: new Date().getTime() };
},
collect: (monitor: any) => ({
isDragging: monitor.isDragging(),
}),
canDrag: () => {
return draggable;
},
});
return (
<Tag
ref={drag}
className={'formula-left-constant-tag-item'}
>
{tag.title}
</Tag>
);
};
return(
<div className={'left'}>
{
dataSource?.map(item=><TagItem key={item.key} tag={item} draggable="true" />)
}
</div>
)
}
export default Left;
3、Right.tsx
import React, { useRef, useState } from 'react';
import { Tag } from 'antd';
import {DragSourceMonitor, useDrag, useDrop} from 'react-dnd';
type ListItem = {
key: number;
title: string;
id: number;
}
const Right = () => {
const ref = useRef<HTMLDivElement>(null);
const [dataList, setDataList] = useState<ListItem[]>([]);
const [, dropRef] = useDrop({
// 指明该区域允许接收的拖放物。可以是单个,也可以是数组里面的值就是useDrag所定义的type
accept: ['LeftTag'],
drop: (item: API.FormulaItem) => {
if (item) {
setDataList((data: ListItem[])=>{
// 判断现有的数据源是否有id值相同的数据,若有则不做处理,若无则把数据插入到数组尾部
const hasIndex = data.findIndex((v: ListItem) => v.id === item?.id);
if(hasIndex === -1){
return [...data, item] as ListItem[];
}
return [...data] as ListItem[];
})
}
return item;
},
});
const moveTag = (dragIndex: number, hoverIndex: number, item: ListItem) => {
/**
* 1、如果dragIndex 为 undefined,则此时为新增的元素,则此时修改 dataList 中的占位元素的位置即可
* 2、如果此时dragIndex 不为 undefined,则此时为拖拽现有的元素,此时替换 dragIndex 和 hoverIndex 位置的元素即可
*/
if (dragIndex !== undefined) {
// 拖动元素
let data = [...dataList];
let temp = data[dragIndex];
data.splice(dragIndex, 1);
data.splice(hoverIndex, 0, temp);
setDataList(data);
} else {
// 新增元素,如果是函数需要额外加上参数
let data = [...dataList];
data.splice(hoverIndex, 0, item);
setDataList(data);
}
};
const TagItem = ({ tag, index }: any) => {
//实现拖拽
const [, drag] = useDrag({
type: 'RightTag',
collect: (monitor: DragSourceMonitor) => ({
isDragging: monitor.isDragging(),
}),
// item 中包含 index 属性,则在 drop 组件 hover 和 drop 是可以根据第一个参数获取到 index 值
item: { ...tag, index },
});
const [, drop] = useDrop({
// 指明该区域允许接收的拖放物。可以是单个,也可以是数组里面的值就是useDrag所定义的type
accept: ['LeftTag', 'RightTag'],
drop: (item: any) => {
if (!ref.current) return;
let dragIndex = item.index;
let hoverIndex = index;
// 拖拽元素下标与鼠标悬浮元素下标一致时,不进行操作
if (dragIndex === hoverIndex) {
return;
}
// 执行 move 回调函数
moveTag(dragIndex, hoverIndex, item);
item.index = hoverIndex;
},
});
drag(drop(ref));
return (
<Tag
ref={ref}
>
{tag.title}
</Tag>
);
};
return (
<div className={'right'} ref={dropRef}>
{
dataList?.map((item, index)=><TagItem key={item.key} tag={item} index={index} />)
}
</div>
)
}
export default Right;
4、index.less
.content{
display: flex;
justify-content: space-between;
}
.left{
width: 45%;
background-color: #ffffff;
border: 1px dashed #ddd;
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 10px;
}
.right{
width: 45%;
background-color: #ffffff;
border: 1px dashed #ddd;
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 10px;
}
至此一个简单的tag拖动示例就实现了,既能拖动tag到容器里面又能改变tag的顺序。