QuickStart
Index
import React, { useState, useCallback } from "react";
import styles from "./index.module.less";
import { errorBoundary } from "@utils/errorBoundary/index";
// 拖拽
import { DndProvider } from "react-dnd";
// import { HTML5Backend } from "react-dnd-html5-backend"; // Html
import { TouchBackend } from "react-dnd-touch-backend"; // Native
import Example from "./example";
const Index = () => {
return (
<div>
<div className={styles.title}>拖拽组件</div>
<DndProvider backend={TouchBackend}>
<Example />
</DndProvider>
</div>
);
};
export default errorBoundary(Index);
Example
import React, { useEffect, useState, useCallback } from "react";
import styles from "./index.module.less";
import { errorBoundary } from "@utils/errorBoundary/index";
import update from "immutability-helper";
import Card from "./card";
interface Item {
id: number;
text: string;
}
const style = {
width: 400,
};
const Example = () => {
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 moveCard = useCallback((dragIndex: number, hoverIndex: number) => {
setCards((prevCards: Item[]) =>
update(prevCards, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, prevCards[dragIndex] as Item],
],
})
);
}, []);
const renderCard = useCallback(
(card: { id: number; text: string }, index: number) => {
return (
<Card
key={card.id}
index={index}
id={card.id}
text={card.text}
moveCard={moveCard}
/>
);
},
[]
);
return (
<div>
<div style={style}>{cards.map((card, i) => renderCard(card, i))}</div>
</div>
);
};
export default errorBoundary(Example);
Card
import React, { useEffect, useState, useCallback,useRef } from 'react'
import styles from './index.module.less'
import { errorBoundary } from '@utils/errorBoundary/index'
import type { Identifier, XYCoord } from 'dnd-core'
import { useDrag, useDrop } from 'react-dnd'
const ItemTypes = {
CARD: 'card',
}
interface CardProps {
id: any
text: string
index: number
moveCard: (dragIndex: number, hoverIndex: number) => void
}
interface DragItem {
index: number
id: string
type: string
}
const style = {
border: '1px dashed gray',
padding: '0.5rem 1rem',
marginBottom: '.5rem',
backgroundColor: 'white',
cursor: 'move',
}
const Card = (props:CardProps) => {
const {id, text, index, moveCard } = props
const ref = useRef<HTMLDivElement>(null)
const [{ handlerId }, drop] = useDrop<DragItem,void,{ handlerId: Identifier | null }>(
{
accept: ItemTypes.CARD,
collect(monitor) {
return {
handlerId: monitor.getHandlerId(),
}
},
hover(item: DragItem, monitor) {
if (!ref.current) {
return
}
const dragIndex = item.index
const hoverIndex = index
//不要用项目本身替换项目
if (dragIndex === hoverIndex) {
return
}
// 确定屏幕上的矩形
const hoverBoundingRect = ref.current?.getBoundingClientRect()
// 获取垂直中间
const hoverMiddleY =
(hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
// 确定鼠标位置
const clientOffset = monitor.getClientOffset()
// 将像素置于顶部
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top
// 仅当鼠标越过项目高度的一半时才执行移动
// 向下拖动时,仅在光标低于50%时移动
// 向上拖动时,仅在光标高于50%时移动
// 向下拖动
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return
}
// 向上拖动
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return
}
// 实际执行操作
moveCard(dragIndex, hoverIndex)
// 注意:我们在这里修改监控项!
// 一般来说,最好避免突变
// 避免不必要的索引搜索
item.index = hoverIndex
},
})
const [{ isDragging }, drag] = useDrag({
type: ItemTypes.CARD,
item: () => {
return { id, index }
},
collect: (monitor: any) => ({
isDragging: monitor.isDragging(),
}),
})
// 执行拖拽
drag(drop(ref))
const opacity = isDragging ? 0.5 : 1
return (
<div
style={{ ...style, opacity }}
data-handler-id={handlerId}
ref={ref}>
{text}
</div>
)
}
export default errorBoundary(Card)