開始
最近剛剛做完自定義表單的組件,分享一下拖拽排序。
效果圖

react-dnd使用說明
必須是這樣格式的,不然會報 找不到上下文
<DndProvider backend={HTML5Backend}>
<useDrop>
<useDrag></useDrag>
</useDrop>
</DndProvider>
准備工作
官方文檔
https://react-dnd.github.io/react-dnd/about
安裝react-dnd、react-dnd-html5-backend
npm install react-dnd react-dnd-html5-backend
開始開發
項目是基於ts的,不需要可自行刪除
定義父組件
const Drag:React.FC<{}> = props => {
return (
<div className="drag-wrapper">
{/* DndProvider組件提供了react-dnd的功能,必須通過backend綁定HTML5Backend*/}
<DndProvider backend={HTML5Backend}>
<DragSortComponent/>
</DndProvider>
</div>
)
}
export default Drag
定義內容組件
const DragSortComponent:React.FC<{}> = props => {
const [list,setList] = useState<{id:number,name:string}[]>(List)
const [itemClass, setItemClass] = useState<{ key: number | null; value: string }>({
key: null,
value: '',
})
// 拖拽后的值
const sortItems = useRef<{ dragRow: any; placeRow: any; posi: string }>({
dragRow: {},
placeRow: {},
posi: '',
})
// 拖拽結束后的方法
const onDrop = (item: any, monitor: DropTargetMonitor) => {
const { dragRow, placeRow, posi } = sortItems.current
let _map: any[] = JSON.parse(JSON.stringify(list))
let index1 = _map.findIndex(v => v.id === dragRow.id) // 拖拽的itemIndex
_map.splice(index1, 1) // 先刪掉拖拽的,在獲取放置的
let index = _map.findIndex(v => v.id === placeRow.id) // 放置的itemIndex
if (index !== -1 && index1 !== -1) {
_map.splice(posi === 'bottom' ? index + 1 : index, 0, dragRow)
setList(() => _map)
}
}
// DragSortItemComponent組件是通用排序組件,所以需要在父組件在定義一個useDrop,來改變數據 必須掛載在父級div
const [, drop] = useDrop({
accept: 'sort', // 必須和拖拽的accept一致
drop: onDrop,
collect: monitor => ({
isOver: monitor.isOver({ shallow: true }),
canDrop: monitor.canDrop(),
}),
})
const onItemDragClass = (key:number,value:string) => {
if (itemClass.value !== value) {
setItemClass(() => {
let data = { key, value }
return data
})
}
}
const onSortItemChange = (dragRow: any, placeRow: any, posi: string) => {
sortItems.current = { dragRow, placeRow, posi }
}
return (
<div className="drag-sort-component-wrapper" ref={drop}>
{
list.map(v => (
<DragSortItemComponent
key={v.id}
row={v}
onItemDragClass={onItemDragClass}
onSortItemChange={onSortItemChange}
keyName="id"
>
<div className={['drag-item ',itemClass.key === v.id ? itemClass.value : ''].join(' ')}>{v.name}</div>
</DragSortItemComponent>
))
}
</div>
)
}
定義拖拽排序組件
/**
* 通用拖拽排序的容器
* @param row 當前行
* @param onItemDragClass 拖拽過程的樣式 top | bottom
* @param onSortItemChange 拖拽結束后返回的值 dragRow 當前拖拽 placeRow 放置的,posi 位置 top | bottom
* @param keyName 鍵名
*/
type IProps = {
row: any,
onItemDragClass: (key:number,value:string) => void,
onSortItemChange: (dragRow: any, placeRow: any, posi: string) => void // 排序后
keyName: string
}
const DragSortItemComponent:React.FC<IProps> = props => {
const { row,onItemDragClass,onSortItemChange,keyName } = props
const ref = useRef<HTMLDivElement>(null)
/**
* 拖拽容器
*/
const [, drop] = useDrop({
// 定義拖拽的類型
accept: 'sort',
drop: (item, monitor) => {
const didDrop = monitor.didDrop()
if (didDrop) {
return
}
},
canDrop: (item, mointor) => {
// 阻止默認拖拽釋放
onItemDragClass(row[keyName], '')
return false
},
hover: (item: any, monitor) => {
const didHover = monitor.isOver({ shallow: true })
if (didHover) {
// 拖拽目標的id
const dragIndex = item[keyName]
// 放置目標id 可以用index | id 只要是number,數據里唯一的就可以
const hoverIndex = row[keyName]
// 如果一樣不處理
if (dragIndex === hoverIndex) {
onItemDragClass(row[keyName], '')
return
}
// 獲取放置的位置
const hoverBoundingRect = ref.current?.getBoundingClientRect() as DOMRect
// 獲取放置的Y軸中點
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
// 獲取拖拽目標偏移量
const clientOffset = monitor.getClientOffset() as XYCoord
const hoverClientY = clientOffset.y - hoverBoundingRect.top
if (dragIndex !== hoverIndex) {
if (hoverMiddleY < hoverClientY) {
onItemDragClass(row[keyName], 'bottom')
} else {
onItemDragClass(row[keyName], 'top')
}
// 如果不做成通用拖拽容器,把參數存起來,把這個放在useDrag的end方法里,
onSortItemChange(item, row, hoverMiddleY < hoverClientY ? 'bottom' : 'top')
}
}
},
collect: monitor => ({
isOver: monitor.isOver({ shallow: true }),
canDrop: monitor.canDrop(),
}),
})
/**
* 定義拖拽
* isDragging 是否拖拽
*/
const [{ isDragging }, drag] = useDrag({
item: { ...row, type: 'sort'},
end: () => {
// onSortItemChange(item, row, hoverMiddleY < hoverClientY ? 'bottom' : 'top')
},
collect: monitor => ({
isDragging: monitor.isDragging(),
didDrop: monitor.isDragging(),
}),
})
drop(drag(ref))
return (
<div ref={ref} style={{ opacity: isDragging ? 0 : 1 }}>
{props.children}
</div>
)
}
