# 安装依赖
yarn add react-dnd react-dnd-html5-backend // 拖拽库
yarn add immutability-helper // 官方推荐的不可变数据更改库
1
2
2
# 使用
import { memo, useCallback, useEffect, useState } from "react";
import DndContainer from "../../../components/DndContainer";
import DndDragItem from "../../../components/DndDragItem";
import type { onDragEndCallbackData } from "../../../components/DndContainer";
import update from "immutability-helper";
const Banner = () => {
const [cards, setCards] = useState([
{
id: 1,
text: "Write a cool JS library",
},
{
id: 2,
text: "Make it generic enough",
},
{
id: 3,
text: "Write README",
},
{
id: 4,
text: "Create some examples",
},
{
id: 5,
text: "Spam in Twitter and IRC to promote it (note that this element is taller than the others)",
},
{
id: 6,
text: "???",
},
{
id: 7,
text: "PROFIT",
},
]);
const onDragEnd = (data: onDragEndCallbackData) => {
const { item, dragIndex, hoverIndex } = data;
setCards(
update(cards, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, item],
],
})
);
};
const renderTableRow = useCallback((card: { id: number; text: string }, index: number) => {
return (
<DndDragItem key={card.id} id={`${card.id}`}>
<div>{text}</div>
</DndDragItem>
);
}, []);
return (
<DndContainer data={cards} onDragEnd={onDragEnd}>
{cards.map((item, index) => renderTableRow(item, index))}
</DndContainer>
);
};
export default Banner;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# 容器组件
import { createContext, useContext, useState, useCallback } from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
export const formatArrByIndex = (arr: any[], dragIndex: number, hoverIndex: number) => {
const _arr = [...arr];
_arr.splice(dragIndex, 1, ..._arr.splice(hoverIndex, 1, _arr[dragIndex]));
return _arr;
};
export declare type onDragEndCallbackData = {
data: any;
item: any;
dragId: string;
dragIndex: number;
hoverIndex: number;
};
type Props = {
children: React.ReactNode;
data: any[];
onDragEnd?: (data: onDragEndCallbackData) => void;
};
interface DndProviderContext {
moveitem: (id: string, atIndex: number) => void;
finditem: (id: string) => any;
}
const DndProviderContext = createContext<DndProviderContext | null>(null);
export const useDndProviderContext = () => {
const context = useContext(DndProviderContext);
if (!context) throw new Error("useDndProviderContext must be use inside DndProviderContext");
return context;
};
const DndContainer = ({ children, data, onDragEnd }: Props) => {
const dataSourse = data;
const [items, setitems] = useState(data);
const finditem = useCallback(
(id: string) => {
const item = items.filter((c) => `${c.id}` === id)[0];
return {
item,
index: items.indexOf(item),
};
},
[items]
);
const moveitem = useCallback(
(id: string, atIndex: number) => {
const { item, index } = finditem(id);
const data = formatArrByIndex(dataSourse, index, atIndex);
onDragEnd && onDragEnd({ data, item, dragId: id, dragIndex: index, hoverIndex: atIndex });
},
[finditem, items, setitems]
);
return (
<DndProvider backend={HTML5Backend}>
<DndProviderContext.Provider value={{ finditem, moveitem }}>
{children}
</DndProviderContext.Provider>
</DndProvider>
);
};
export default DndContainer;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 拖拽组件
import type { FC } from "react";
import { useRef, memo } from "react";
import { useDrag, useDrop } from "react-dnd";
import { useDndProviderContext } from "./DndContainer";
import { Box } from "@mui/material";
type Props = { children: React.ReactNode; id: string };
interface mainItem {
id: string;
originalIndex: number;
}
const ItemTypes = {
ITEM: "TableRow",
};
const DndDragItem: FC<Props> = memo(({ children, id }) => {
const ref = useRef<HTMLDivElement>(null);
const { finditem, moveitem } = useDndProviderContext();
const originalIndex = finditem(id).index;
const [{ isDragging }, drag] = useDrag(
() => ({
type: ItemTypes.ITEM,
item: { id, originalIndex },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
end: (item, monitor) => {
const { id: droppedId, originalIndex } = item;
const didDrop = monitor.didDrop();
if (!didDrop) {
// 拖放失败时复原数据
moveitem(droppedId, originalIndex);
}
},
}),
[id, originalIndex, moveitem]
);
const [, drop] = useDrop(
() => ({
accept: ItemTypes.ITEM,
hover({ id: draggedId }: mainItem) {
if (draggedId !== id) {
const { index: overIndex } = finditem(id);
moveitem(draggedId, overIndex);
}
},
}),
[finditem, moveitem]
);
const opacity = isDragging ? 0 : 1;
drag(drop(ref));
return (
<Box
sx={{
opacity: opacity,
}}
ref={ref}
>
{children}
</Box>
);
});
export default DndDragItem;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73