react-dnd?API拖拽工具詳細用法示例
前言
最近公司準備開發(fā)一個審批流系統(tǒng),其中會用到拖拽工具來搭建流程,關于拖拽的實現(xiàn)我們選擇了react-dnd這個庫,本文總結了react-dnd API的詳細用法,并附上不同場景的demo,希望對大家有用。
概念
React DnD 是一組 React 高階組件,使用的時候只需要使用對應的 API 將目標組件進行包裹,即可實現(xiàn)拖動或接受拖動元素的功能。
在拖動的過程中,不需要開發(fā)者自己判斷拖動狀態(tài),只需要在傳入的 spec 對象中各個狀態(tài)屬性中做對應處理即可,因為react-dnd使用了redux管理自身內部的狀態(tài)。
Some of these concepts resemble the Flux and Redux architectures.
This is not a coincidence, as React DnD uses Redux internally.
值得注意的是,react-dnd并不會改變頁面的視圖,它只會改變頁面元素的數(shù)據(jù)流向,因此它所提供的拖拽效果并不是很炫酷的,我們可能需要寫額外的視圖層來完成想要的效果,但是這種拖拽管理方式非常的通用,可以在任何場景下使用,非常適合用來定制。
核心API
介紹實現(xiàn)拖拽和數(shù)據(jù)流轉的核心API,這里以Hook為例。
DndProvider
如果想要使用 React DnD,首先需要在外層元素上加一個 DndProvider。
import { HTML5Backend } from 'react-dnd-html5-backend'; import { DndProvider } from 'react-dnd'; <DndProvider backend={HTML5Backend}> <TutorialApp /> </DndProvider>
DndProvider 的本質是一個由 React.createContext 創(chuàng)建一個上下文的容器(組件),用于控制拖拽的行為,數(shù)據(jù)的共享,類似于react-redux的Provider。
Backend
上面我們給DndProvider傳的參數(shù)是一個backend,那么這里來解釋一下什么是backend
React DnD 將 DOM 事件相關的代碼獨立出來,將拖拽事件轉換為 React DnD 內部的 redux action。由于拖拽發(fā)生在 H5 的時候是 ondrag,發(fā)生在移動設備的時候是由 touch 模擬,React DnD 將這部分單獨抽出來,方便后續(xù)的擴展,這部分就叫做 Backend。它是 DnD 在 Dom 層的實現(xiàn)。
- react-dnd-html5-backend : 用于控制html5事件的backend
- react-dnd-touch-backend : 用于控制移動端touch事件的backend
- react-dnd-test-backend : 用戶可以參考自定義backend
useDrag
讓DOM實現(xiàn)拖拽能力的構子,官方用例如下
import { DragPreviewImage, useDrag } from 'react-dnd'; export const Knight: FC = () => { const [{ isDragging }, drag, preview] = useDrag( () => ({ type: ItemTypes.KNIGHT, collect: (monitor) => ({ isDragging: !!monitor.isDragging() }) }), [] ); return ( <> <DragPreviewImage connect={preview} src={knightImage} /> <div ref={drag} > ? </div> </> ); };
useDrag返回三個參數(shù)
第一個返回值是一個對象
表示關聯(lián)在拖拽過程中的變量,需要在傳入useDrag的規(guī)范方法的collect屬性中進行映射綁定,比如:isDraging,canDrag等
第二個返回值
代表拖拽元素的ref
第三個返回值
代表拖拽元素拖拽后實際操作到的dom
useDrag傳入兩個參數(shù)
- 第一個參數(shù),是一個對象,是用于描述了drag的配置信息,常用屬性
type
: 指定元素的類型,只有類型相同的元素才能進行drop操作
item
: 元素在拖拽過程中,描述該對象的數(shù)據(jù),如果指定的是一個方法,則方法會在開始拖拽時調用,并且需要返回一個對象來描述該元素。
end(item, monitor)
: 拖拽結束的回調函數(shù),item表示拖拽物的描述數(shù)據(jù),monitor表示一個 DragTargetMonitor 實例
isDragging(monitor)
:判斷元素是否在拖拽過程中,可以覆蓋Monitor對象中的isDragging方法,monitor表示一個 DragTargetMonitor 實例
isDragging: (monitor) => { return monitor.getItem() ? index === monitor.getItem().index : false; }, collect: (monitor: any) => ({ //當傳入isDragging方法時,monitor.isDragging()方法指代傳入的方法 isDragging: monitor.isDragging(), }),
canDrag(monitor)
:判斷是否可以拖拽的方法,需要返回一個bool值,可以覆蓋Monitor對象中的canDrag方法,與isDragging同理,monitor表示一個 DragTargetMonitor 實例
collect
:它應該返回一個描述狀態(tài)的普通對象,然后返回以注入到組件中。它接收兩個參數(shù),一個 DragTargetMonitor 實例和拖拽元素描述信息item
- 第二個參數(shù)是一個數(shù)組,表示對方法更新的約束,只有當數(shù)組中的參數(shù)發(fā)生改變,才會重新生成方法,基于react的useMemo實現(xiàn)
DragSourceMonitor對象
DragSourceMonitor是傳遞給拖動源的收集函數(shù)的對象。它的方法允許您獲取有關特定拖動源的拖動狀態(tài)的信息。 常用的方法: canDrag()
:描述元素是否可以拖拽,返回一個bool值
isDragging()
:判斷元素是否在拖拽過程中,返回一個bool值
getItemType()
:獲取元素的類型,返回一個bool值
getItem()
:獲取元素的描述數(shù)據(jù),返回一個對象
getDropResult()
:拖拽結束,返回拖拽結果的構子,可以拿到從drop元素中返回的數(shù)據(jù)
didDrop()
: 拖拽結束,元素是否放置成功,返回一個bool值
getDifferenceFromInitialOffset()
: 獲取相對于拖拽起始位置的相對偏移坐標。
useDrop
實現(xiàn)拖拽物放置的鉤子,官方用例如下
function BoardSquare({ x, y, children }) { const black = (x + y) % 2 === 1 const [{ isOver }, drop] = useDrop(() => ({ accept: ItemTypes.KNIGHT, drop: () => moveKnight(x, y), collect: monitor => ({ isOver: !!monitor.isOver(), }), }), [x, y]) return ( <div ref={drop} style={{ position: 'relative', width: '100%', height: '100%', }} > .... </div>, ) } export default BoardSquare
useDrag返回兩個參數(shù)
- 第一個返回值是一個對象,表示關聯(lián)在拖拽過程中的變量,需要在傳入useDrop的規(guī)范方法的collect屬性中進行映射綁定
- 第二個返回值代表放置元素的ref
useDrop傳入一個參數(shù)
用于描述drop的配置信息,常用屬性
accept
: 指定接收元素的類型,只有類型相同的元素才能進行drop操作
drop(item, monitor)
: 有拖拽物放置到元素上觸發(fā)的回調方法,item表示拖拽物的描述數(shù)據(jù),monitor表示 DropTargetMonitor實例,該方法返回一個對象,對象的數(shù)據(jù)可以由拖拽物的monitor.getDropResult
方法獲得
hover(item, monitor)
:當拖住物在上方hover時觸發(fā),item表示拖拽物的描述數(shù)據(jù),monitor表示 DropTargetMonitor實例,返回一個bool值
canDrop(item, monitor)
:判斷拖拽物是否可以放置,item表示拖拽物的描述數(shù)據(jù),monitor表示 DropTargetMonitor實例,返回一個bool值
DropTargetMonitor對象
DropTargetMonitor是傳遞給拖放目標的收集函數(shù)的對象。它的方法允許您獲取有關特定拖放目標的拖動狀態(tài)的信息。 常用的方法:
canDrop()
:判斷拖拽物是否可以放置,返回一個bool值
isOver(options)
: 拖拽物掠過元素觸發(fā)的回調方法,options表示拖拽物的options信息
getItemType()
:獲取元素的類型,返回一個bool值
getItem()
:獲取元素的描述數(shù)據(jù),返回一個對象
didDrop()
: 拖拽結束,元素是否放置成功,返回一個bool值
getDifferenceFromInitialOffset()
: 獲取相對于拖拽起始位置的相對偏移坐標。
數(shù)據(jù)流轉
看了API之后,實際上不能很好的認識到每個狀態(tài)和每個方法的工作流程,所以,我這里畫了一張圖,幫助你更清晰的看到它的數(shù)據(jù)是如何流動的。
然后我們通過一個demo來更深刻的認識這個過程
這里我們定義了幾個單詞,然后通過拖拽,將它放入對應的分組里面
單詞代碼
const Word: FC = ({ type, text, id, ...props }: any) => { const [offsetX, setOffsetX] = useState(0); const [offsetY, setOffsetY] = useState(0); const [{ isDragging }, drag]: any = useDrag(() => ({ type, item: { id, type }, end(item, monitor) { let top = 0, left = 0; if (monitor.didDrop()) { const dropRes = monitor.getDropResult() as any; //獲取拖拽對象所處容器的數(shù)據(jù),獲取坐標變化 if (dropRes) { top = dropRes.top; left = dropRes.left; } //這里必須寫成函數(shù)的傳入方式,否則無法獲取上一個state setOffsetX((offsetX) => offsetX + left); setOffsetY((offsetY) => offsetY + top); } else { // 移出則回到原位 setOffsetX(0); setOffsetY(0); } }, collect: (monitor) => ({ isDragging: !!monitor.isDragging(), }), })); return ... // dom ); };
分組代碼
function Classification({ type, title }: any) { const [{ isOver, canDrop }, drop] = useDrop( () => ({ accept: type, drop(_item: any, monitor: any) { // 獲取每一次放置相對于上一次的偏移量 const delta = monitor.getDifferenceFromInitialOffset(); const left = Math.round(delta.x); const top = Math.round(delta.y); // 回傳給drag return { top, left }; }, canDrop: (_item, monitor) => { const item = monitor.getItem() as any; return item.type === type; }, collect: (monitor) => ({ isOver: !!monitor.isOver(), canDrop: !!monitor.canDrop(), }), }), [], ); return ... // dom )
完整demo戳鏈接:github.com/AdolescentJ…
拖拽預覽
受限于瀏覽器API的控制,拖拽元素在開始拖拽之后,只能保持其本身的一個樣式,不能使用其他樣式預覽,針對此,react-dnd為我們提供了兩種解決方法。
DragPreviewImage
react-dnd提供的DragPreviewImage組件,讓我們在拖拽的時候,可以以圖片的形式預覽拖拽的元素
它接收兩個參數(shù),一個是useDrag返回的預覽ref,一個是需要預覽的圖片
使用
import { useDrag, DragPreviewImage } from 'react-dnd'; import apple from '../../assets/apple.png'; const DragPreviewImg = () => { const [{ isDragging }, drag, preview] = useDrag({ type: 'DragDropBox', collect: (monitor) => ({ isDragging: monitor.isDragging(), }), }); return ( <> <DragPreviewImage connect={preview} src={apple} /> <div className='card_drag' ref={drag} style={{ opacity: isDragging ? 0.5 : 1, }} > drag item prview an apple </div> </> ); }; export default DragPreviewImg;
效果
完整demo戳鏈接:github.com/AdolescentJ…
使用圖片來預覽拖拽元素確實可以解決一部分問題,但在實際場景中,拖拽的元素可能會很多,我們也不能找UI把所有類型的圖都給一張,并且很多比較復雜的dom圖片是不能取代的
所以要展示更加定制化的預覽樣式,我們可以使用下面這種。
useDragLayer
useDragLayer是一個鉤子,它允許你使用dom的方式自定義拖拽元
它的原理是,監(jiān)聽你的拖拽狀態(tài),在拖拽的時候,可以取到你拖拽狀態(tài)的數(shù)據(jù),并且它會創(chuàng)建一個你想預覽的dom,然后我們可以可以通過拖拽的狀態(tài)來改變它的樣式,達到取代預覽的效果。
使用
const CustomDragLayer = (props: any) => { //monitor 是drag的 monitor const { itemType, isDragging, item, initialOffset, currentOffset } = useDragLayer((monitor) => ({ item: monitor.getItem(), itemType: monitor.getItemType(), initialOffset: monitor.getInitialSourceClientOffset(), currentOffset: monitor.getSourceClientOffset(), isDragging: monitor.isDragging(), })); if (!isDragging) { return null; } return ( <div style={layerStyles}> <div className='card_drag'>這里是預覽樣式</div> </div> ); }
預覽
完整demo戳鏈接:github.com/AdolescentJ…
其他使用場景
除了上面的例子,還有非常多的案例
批量拖拽
可以選擇多個元素進行拖拽
拖拽排序
可以拖拽元素放置排序
完整demo戳鏈接:github.com/AdolescentJ…
最后
感謝你能看到這里,本文總結了react-dnd的API的使用以及常見的場景,希望對你有所幫助,后續(xù)會一直更新,當然,如果可以的話不妨留一個贊再走呢。
參考鏈接
react-dnd.github.io/react-dnd/d…
http://www.dbjr.com.cn/article/265220.htm
以上就是react-dnd API拖拽工具詳細用法示例的詳細內容,更多關于react-dnd API拖拽工具的資料請關注腳本之家其它相關文章!
相關文章
淺談react?16.8版本新特性以及對react開發(fā)的影響
本文主要介紹了react?16.8版本新特性以及對react開發(fā)的影響,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03