使用react-dnd編寫(xiě)一個(gè)可拖拽排列的list
今天的需求來(lái)啦:做一個(gè)可以通過(guò)拖拽排列的list。最終效果是這樣:

開(kāi)始之前,我找了個(gè)市面上比較主流的工具庫(kù),做個(gè)比對(duì):

這個(gè)是atlassian用來(lái)開(kāi)發(fā)jira看板功能的庫(kù),有動(dòng)畫(huà)效果,很漂亮,確實(shí)對(duì)得起這個(gè)包的名字。優(yōu)點(diǎn)是相對(duì)易用,而且好看,缺點(diǎn)是僅適合開(kāi)發(fā)list一類(lèi)的組件,而且?guī)啄昵熬鸵呀?jīng)不再維護(hù)了,對(duì)于新版的函數(shù)式react可能不太兼容。
- dnd-kit這個(gè)是atlassian在棄用
react-beautiful-dnd后重新維護(hù)的一個(gè)版本,效果是一樣的美麗,如果是web端的,還是很推薦的。 - reat-dnd這是個(gè)比較全面的dnd庫(kù),可以實(shí)現(xiàn)各種功能,要注意的是這是基于H5的開(kāi)發(fā),如果你開(kāi)發(fā)的端不是H5,可能不適用。
react-dnd的官方文檔介紹了如何做一個(gè)棋盤(pán),類(lèi)似的,這篇文章將介紹如何用react-dnd做一個(gè)draggable list。
Draggable
首先將list中的每一個(gè)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;
這樣我們?cè)赿emo中就能看到,這個(gè)組件就可以用鼠標(biāo)拖了:

Droppable
我們?cè)賹ist容器做成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; //<--- 具體邏輯后面再寫(xiě),這里先設(shè)為true,即容器內(nèi)部一直是可以drop的。
}
const moveItem = () => {
console.log('moveItem'); //<--- 具體邏輯后面再寫(xiě)。
}
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;
這樣頁(yè)面上,我再容器內(nèi)部拖動(dòng)時(shí),isOver為true,canDrop為true,拖到容器外isOver就變成false了:

既是draggable,又是droppable
當(dāng)有多個(gè)list item,可以通過(guò)拖拽調(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
// 如果是向下拖,只有當(dāng)hover的坐標(biāo)小于自身高度的一半時(shí),繼續(xù)保持drag,否則相當(dāng)于要挪動(dòng)其他的list item,給當(dāng)前的item騰個(gè)地方
// if dragging down, continue only when hover is smaller than middle Y
if (dragIndex < hoverIndex && hoverActualY < hoverMiddleY) return
// 如果是向上拖,只有當(dāng)hover的坐標(biāo)大于自身高度的一半時(shí),繼續(xù)保持drag,否則相當(dāng)于要挪動(dòng)其他的list item,給當(dāng)前的item騰個(gè)地方
if (dragIndex > hoverIndex && hoverActualY > hoverMiddleY) return
moveListItem(dragIndex, hoverIndex)
item.index = hoverIndex
},
});
那么如何讓一個(gè)list item既是draggable,又是droppable呢?用以下方式:
// ListItem.tsx const ref = useRef(null); const dragDropRef = dragRef(dropRef(ref))
重排list
上一步我們把moveListItem傳給了父組件,那么在父組件中,我們可以這樣來(lái)改變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],
)
這樣我們就能得到這樣的效果:

這樣一個(gè)sortable list就大功告成啦!
總結(jié)
現(xiàn)在這個(gè)list是沒(méi)有任何動(dòng)畫(huà)的,要是listItem在移動(dòng)的過(guò)程中是有個(gè)過(guò)渡效果的,就顯得絲滑多了,那么接下來(lái)我將用react-flip-move將動(dòng)畫(huà)效果加到每個(gè)item中。
到此這篇關(guān)于使用react-dnd編寫(xiě)一個(gè)可拖拽排列的list的文章就介紹到這了,更多相關(guān)react-dnd可拖拽排列的list內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React?split實(shí)現(xiàn)分割字符串的使用示例
當(dāng)我們需要將一個(gè)字符串按照指定的分隔符進(jìn)行分割成數(shù)組時(shí),我們可以在組件的生命周期方法中使用split方法來(lái)實(shí)現(xiàn)這個(gè)功能,本文就來(lái)介紹一下,感興趣的可以了解下2023-10-10
React中重新實(shí)現(xiàn)強(qiáng)制實(shí)施表單的流程步驟
這篇文章主要介紹了React中重新實(shí)現(xiàn)強(qiáng)制實(shí)施表單的流程步驟,就像設(shè)計(jì)人員一樣,在添加邏輯之前,您需要為不同的狀態(tài)“模擬”或創(chuàng)建“模擬”,例如,這里只是表單的視覺(jué)部分的模擬,文中通過(guò)代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-05-05
React 組件的狀態(tài)下移和內(nèi)容提升的操作方法
這篇文章主要介紹了React 組件的狀態(tài)下移和內(nèi)容提升,通過(guò)代碼講解了渲染性能的組件問(wèn)題結(jié)合實(shí)例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下2022-11-11
Redux Toolkit 實(shí)戰(zhàn)使用指南
Redux Toolkit 是 Redux 官方推出的高效開(kāi)發(fā)工具集,本文就來(lái)介紹一下Redux Toolkit 實(shí)戰(zhàn)使用指南,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2026-03-03
React 首頁(yè)加載慢問(wèn)題性能優(yōu)化案例詳解
這篇文章主要介紹了React 首頁(yè)加載慢問(wèn)題性能優(yōu)化案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
React配置代理服務(wù)器的5種方法及使用場(chǎng)景
這篇文章主要介紹了React配置代理服務(wù)器的5種方法,無(wú)論使用哪種方法,都需要確保代理服務(wù)器的地址和端口正確,并且在配置完成后重新啟動(dòng)React開(kāi)發(fā)服務(wù)器,使配置生效,需要的朋友可以參考下2023-08-08
react-router?v6實(shí)現(xiàn)權(quán)限管理+自動(dòng)替換頁(yè)面標(biāo)題的案例
這篇文章主要介紹了react-router?v6實(shí)現(xiàn)權(quán)限管理+自動(dòng)替換頁(yè)面標(biāo)題,這次項(xiàng)目是有三種權(quán)限,分別是用戶(hù),商家以及管理員,這次寫(xiě)的權(quán)限管理是高級(jí)權(quán)限能訪問(wèn)低級(jí)權(quán)限的所有頁(yè)面,但是低級(jí)權(quán)限不能訪問(wèn)高級(jí)權(quán)限的頁(yè)面,需要的朋友可以參考下2023-05-05
使用react+redux實(shí)現(xiàn)計(jì)數(shù)器功能及遇到問(wèn)題
使用redux管理數(shù)據(jù),由于Store獨(dú)立于組件,使得數(shù)據(jù)管理獨(dú)立于組件,解決了組件之間傳遞數(shù)據(jù)困難的問(wèn)題,非常好用,今天重點(diǎn)給大家介紹使用react+redux實(shí)現(xiàn)計(jì)數(shù)器功能及遇到問(wèn)題,感興趣的朋友參考下吧2021-06-06
每天學(xué)習(xí)一個(gè)hooks?useMount
這篇文章主要為大家介紹了每天學(xué)習(xí)一個(gè)hooks?useMount,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05

