使用react-dnd編寫一個可拖拽排列的list
今天的需求來啦:做一個可以通過拖拽排列的list。最終效果是這樣:

開始之前,我找了個市面上比較主流的工具庫,做個比對:

這個是atlassian用來開發(fā)jira看板功能的庫,有動畫效果,很漂亮,確實對得起這個包的名字。優(yōu)點是相對易用,而且好看,缺點是僅適合開發(fā)list一類的組件,而且?guī)啄昵熬鸵呀?jīng)不再維護了,對于新版的函數(shù)式react可能不太兼容。
- dnd-kit這個是atlassian在棄用
react-beautiful-dnd后重新維護的一個版本,效果是一樣的美麗,如果是web端的,還是很推薦的。 - reat-dnd這是個比較全面的dnd庫,可以實現(xiàn)各種功能,要注意的是這是基于H5的開發(fā),如果你開發(fā)的端不是H5,可能不適用。
react-dnd的官方文檔介紹了如何做一個棋盤,類似的,這篇文章將介紹如何用react-dnd做一個draggable list。
Draggable
首先將list中的每一個item做成draggable組件。
引入useDrag:
// ListItem.tsx
import { styled } from '@mui/material/styles';
import * as React from 'react';
import { useDrag } from 'react-dnd';
const StyledListItem = styled('div', {
shouldForwardProp: prop => prop !== 'isDragging'
})<{ isDragging: boolean }>(({ isDragging, theme }) => ({
opacity: isDragging ? 0.5 : 1,
border: '1px blue solid',
}));
const ListItem = () => {
const [{isDragging}, dragRef] = useDrag(() => ({
type: 'listItem',
collect: monitor => ({
isDragging: !!monitor.isDragging(),
}),
}))
return (
<StyledListItem ref={dragRef} isDragging={isDragging}>
list item
</StyledListItem>
)
}
export default ListItem;
這樣我們在demo中就能看到,這個組件就可以用鼠標拖了:

Droppable
我們再將list容器做成droppable組件,這樣draggable組件才可以放在droppable的list里。
引入useDrop:
// SortableList.tsx
import React, { forwardRef } from 'react';
import { useDrop } from 'react-dnd';
import ListItem from './components/listItem';
const SortableList = () => {
const canMoveItem = () => {
console.log('canMoveItem')
return true; //<--- 具體邏輯后面再寫,這里先設為true,即容器內(nèi)部一直是可以drop的。
}
const moveItem = () => {
console.log('moveItem'); //<--- 具體邏輯后面再寫。
}
const [{ isOver, canDrop }, dropRef] = useDrop(
() => ({
accept: 'listItem',
canDrop: () => canMoveItem(),
drop: () => moveItem(),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop()
})
}),
[canMoveItem, moveItem]
)
return (
<div>
isOver: {String(isOver)}<br />
canDrop: {String(canDrop)}
<div
ref={dropRef}
style={{
position: 'relative',
width: '600px',
height: '400px',
border: '2px purple solid'
}}
>
<ListItem></ListItem>
</div>
</div>
);
}
export default SortableList;
這樣頁面上,我再容器內(nèi)部拖動時,isOver為true,canDrop為true,拖到容器外isOver就變成false了:

既是draggable,又是droppable
當有多個list item,可以通過拖拽調(diào)整順序,這樣我需要將list item也變成droppable:
// ListItem.tsx
// 將list item也變成droppable
const [spec, dropRef] = useDrop({
accept: 'listItem',
hover: (item, monitor) => {
const dragIndex = item.index
const hoverIndex = index
const hoverBoundingRect = ref.current?.getBoundingClientRect()
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
const hoverActualY = monitor.getClientOffset().y - hoverBoundingRect.top
// 如果是向下拖,只有當hover的坐標小于自身高度的一半時,繼續(xù)保持drag,否則相當于要挪動其他的list item,給當前的item騰個地方
// if dragging down, continue only when hover is smaller than middle Y
if (dragIndex < hoverIndex && hoverActualY < hoverMiddleY) return
// 如果是向上拖,只有當hover的坐標大于自身高度的一半時,繼續(xù)保持drag,否則相當于要挪動其他的list item,給當前的item騰個地方
if (dragIndex > hoverIndex && hoverActualY > hoverMiddleY) return
moveListItem(dragIndex, hoverIndex)
item.index = hoverIndex
},
});
那么如何讓一個list item既是draggable,又是droppable呢?用以下方式:
// ListItem.tsx const ref = useRef(null); const dragDropRef = dragRef(dropRef(ref))
重排list
上一步我們把moveListItem傳給了父組件,那么在父組件中,我們可以這樣來改變list的順序:
// Mock data
const PETS = [
{ id: 1, name: 'dog' },
{ id: 2, name: 'cat' },
{ id: 3, name: 'fish' },
{ id: 4, name: 'hamster' },
]
// SortableList.tsx
const moveListItem = useCallback(
(dragIndex, hoverIndex) => {
console.log({dragIndex, hoverIndex})
const dragItem = pets[dragIndex]
const hoverItem = pets[hoverIndex]
// 將PETS數(shù)組中,drag的item和hover的item交換位置
setPets(pets => {
const updatedPets = [...pets]
updatedPets[dragIndex] = hoverItem
updatedPets[hoverIndex] = dragItem
return updatedPets
})
},
[pets],
)
這樣我們就能得到這樣的效果:

這樣一個sortable list就大功告成啦!
總結(jié)
現(xiàn)在這個list是沒有任何動畫的,要是listItem在移動的過程中是有個過渡效果的,就顯得絲滑多了,那么接下來我將用react-flip-move將動畫效果加到每個item中。
到此這篇關(guān)于使用react-dnd編寫一個可拖拽排列的list的文章就介紹到這了,更多相關(guān)react-dnd可拖拽排列的list內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React 組件的狀態(tài)下移和內(nèi)容提升的操作方法
這篇文章主要介紹了React 組件的狀態(tài)下移和內(nèi)容提升,通過代碼講解了渲染性能的組件問題結(jié)合實例代碼給大家講解的非常詳細,需要的朋友可以參考下2022-11-11
react-router?v6實現(xiàn)權(quán)限管理+自動替換頁面標題的案例
這篇文章主要介紹了react-router?v6實現(xiàn)權(quán)限管理+自動替換頁面標題,這次項目是有三種權(quán)限,分別是用戶,商家以及管理員,這次寫的權(quán)限管理是高級權(quán)限能訪問低級權(quán)限的所有頁面,但是低級權(quán)限不能訪問高級權(quán)限的頁面,需要的朋友可以參考下2023-05-05
使用react+redux實現(xiàn)計數(shù)器功能及遇到問題
使用redux管理數(shù)據(jù),由于Store獨立于組件,使得數(shù)據(jù)管理獨立于組件,解決了組件之間傳遞數(shù)據(jù)困難的問題,非常好用,今天重點給大家介紹使用react+redux實現(xiàn)計數(shù)器功能及遇到問題,感興趣的朋友參考下吧2021-06-06

