這個 Star 的拖拽庫有點料,我們一起學學
一、背景
近期在工作中遇到了一個新的需求,該需求需要實現某個組件的拖拽,面對這個需求的第一個項目肯定是問問度娘和用最大同性交友網站進行搜索,最終皇天不負有心人,讓我找到了這個17k star的React拖拽庫——React DnD。
二、簡單使用
2.1 安裝
- npm install react-dnd -S // react-dnd包,其核心包
- npm install react-dnd-html5-backend -S // 拖拽的底層實現所需要的庫
2.2 三個核心點
通過使用React DnD這個庫,我認為里面最有用的部分包含一個組件和兩個Hook API,它們分別是:
- DndProvider組件
- useDrag函數
- useDrop函數
2.2.1 DndProvider組件
如果想讓某一內容使用React DnD的能力,需要將該部分用DndProvider進行包裹,其接收參數如下所示:
- backend:必填。一個React DnD后端。目前官方文檔有三個,分別為:react-dnd-html5-backend、react-dnd-touch-backend、react-dnd-test-backend,但是常用的還是react-dnd-html5-backend。
- context:可選的。用于配置后端的后端上下文。這取決于后端實現。
- options:可選的。用于配置后端的選項對象。這取決于后端實現。
下面來一起看看該組件的簡單使用:
- import {DndProvider} from 'react-dnd';
- import {HTML5Backend} from 'react-dnd-html5-backend';
- function App() {
- return (
- <div className="App">
- <DndProvider backend={HTML5Backend}>
- 此處將放拖拽相關內容
- </DndProvider>
- </div>
- );
- }
- export default App;
2.2.2 useDrag函數
既然知道了整個操縱空間,接下來需要了解的就是從什么位置進行拖拽,該庫提供了useDrag hook API,該元素可以讓一個DOM元素實現拖拽效果。
參數
(1) spec:創(chuàng)建規(guī)范對象的規(guī)范對象或函數,其詳細內容如下所示:
1)type
必須,是一個字符串或Symbol,只有drop和此值相同才可以進行放置;
2)item
必須,用于描述被拖動的數據
3)previewOptions
可選的,一個簡單對象,用于描述拖動預覽選項;
4)options
可選的,一個簡單對象
5)end(item, monitor)
可選的,當拖拽停止,該函數被調用;
6)canDrag(monitor)
可選的,使用它指定當前是否允許拖動;
7)isDragging(monitor)
可選的,默認情況下,只有啟動拖動操作的拖動源才被視為拖動;
8)collect
可選的,監(jiān)聽功能
返回值
返回值是一個數組,數組內容分別為:
collected:一個對象,包含從collect函數收集的屬性,如果collect未定義函數,則返回一個空對象;drag:拖動器的連接器功能,必須附加到DOM的可拖動部分;dragPreview:用于拖動預覽的連接器功能,可以附加到DOM的預覽部分;
與拖動部分建立連接
通過ref屬性,將drag或dragPreview綁定到拖拽源上。
下面一起來看看useDrag部分的使用
- import {useDrag} from 'react-dnd';
- const SourceBox = props => {
- const {children} = props;
- /**
- * 返回的參數
- * collected:一個對象,包含從collect函數收集的屬性,如果collect未定義函數,則返回一個空對象
- * drag:拖動器的連接器功能,必須附加到DOM的可拖動部分
- * dragPreview:用于拖動預覽的連接器功能,可以附加到DOM的預覽部分
- */
- const [collected, drag, dragPreview] = useDrag({
- // 只有drop和此值相同才可以進行放置
- type: 'box',
- // 描述要拖動的數據
- item: {
- detail: '我是可以拖動的數據?。?!'
- },
- // 拖動停止的手end將會被調用
- end: (item, monitor) => {
- // getDropResult()獲取釋放后的結果
- console.log('monitor.getDropResult():', monitor.getDropResult());
- // source是否已經drop在target
- console.log('monitor.didDrop()', monitor.didDrop());
- },
- // 指定當前是否允許拖動,默認允許
- canDrag: monitor => {
- return true;
- },
- // 監(jiān)聽功能
- collect: (monitor, props) => {
- return {
- isDragging: monitor.isDragging()
- };
- }
- });
- return (
- <div ref={drag}>
- {children}
- </div>
- );
- };
- export default SourceBox;
2.2.3 useDrop函數
為了將內容放置到目標位置,提供了useDrop函數,如下所示:
參數
(1) spec:創(chuàng)建規(guī)范對象的規(guī)范對象或函數,其詳細內容如下所示:
1)accept
必須,一個字符串,此放置目標將僅對于指定類型的拖動源產生的項目作出反應;
2)options
可選的,一個普通的對象;
3)drop(item,monitor)
可選的,當兼容項目放在目標時被調用;
4)hover(item,monitor)
可選的,將項目懸停在組件時調用;
5)canDrop(item,monitor)
可選的,用它來指定放置目標是否接受該拖拽內容;
6)collect
可選的,監(jiān)聽功能
返回值
返回值是一個數組,數組內容分別為:
collected:一個對象,包含從collect函數收集的屬性,如果collect未定義函數,則返回一個空對象;drop:一個用于放置目標的連接器函數,必須附加到DOM的放置部分;
與放置部分建立連接
通過ref屬性,將drop與放置部分建立連接。
下面一起來看看useDrop部分的使用
- import {useDrop} from "react-dnd";
- const TargetBox = () => {
- const [collected, drop] = useDrop({
- // 此放置目標將僅對于指定類型的拖動源產生的項目作出反應
- accept: 'box',
- // 當兼容項目放在目標時調用
- drop: (item, monitor) => {
- console.log('我已經被放到目標?。。?)
- },
- // 監(jiān)聽功能
- collect: monitor => {
- return {
- // 是否重疊
- isOver: monitor.isOver(),
- // 是否可以放置
- canDrop: monitor.canDrop(),
- item: monitor.getItem(),
- didDrop: monitor.didDrop()
- };
- }
- });
- return (
- <div ref={drop}>
- <div className="targetBox">
- 這是放置的區(qū)塊
- </div>
- </div>
- );
- };
- export default TargetBox;
2.3 monitor詳細內容
useDrag和useDrop上掛載了很多選項,這些選項中很多存在monitor對象,該對象上掛載了很多方法,下面就簡要概述幾個主要方法,如下所示:
1. drag上的monitor上的方法
2. drop上的monitor上的方法
三、效果圖
拖拽前
拖拽中
拖拽中拖拽的內容跟隨鼠標移動
拖拽后
拖拽釋放鼠標后,一些內容被打印出來,打印的結果是先輸出drop中的內容再輸出end中的內容,所以我們想做一些處理最后在SourceBox中進行處理,如果在drop中改變React相關的數據會報錯。
四、學習感悟
這個庫的資料千篇一律,在使用過程中遇到了一些坑,接下來與各位老鐵分享一下這些坑,防止后續(xù)深陷其中。
- end方法的調用時機晚于drop的調用時機,所以只有在end中做釋放后的數據處理才能保證系統(tǒng)的正確性,如果在drop中就更新state或React redux中數據,會引發(fā)錯誤;
- item數據是從Drag到Drop之間的橋梁,在drag中定義的item數據可以通過monitor.getItem()獲取;
- drop回調的返回值是從Drop到Drag之間的橋梁,在end中可以通過monitor.getDropResult()其返回值;
- 一些掛載在monitor上的位置函數并不一定適用于所有的場景,需要引入DOM相關的位置操作。
本文轉載自微信公眾號「前端點線面」,可以通過以下二維碼關注。轉載本文請聯(lián)系前端點線面公眾號。