本文詳細講解了 react-dnd 的 API 以及用法,並且附上了可供參考的 Demo,希望能夠給需要的朋友提供一下幫助。
一、概念
React DnD 是一組 React 高階組件,使用的時候只需要使用對應的 API 將目標組件進行包裹,即可實現拖動或接受拖動元素的功能。將拖動的事件轉換成對象中對應狀態的形式,不需要開發者自己判斷拖動狀態,只需要在傳入的 spec 對象中各個狀態屬性中做對應處理即可。剛剛接觸可能難以理解,真正熟悉用法之后會感覺很方便。
本文
Demo
地址:react-dnd-dustbin。如有幫助,歡迎 Star。
二、DragSource:使組件能夠被拖拽
使用
DragSource
包裹住組件,使其可以進行拖動。
使用方式
-
import React, { Component } from 'react';
-
import { DragSource } from 'react-dnd';
-
-
const spec = {
-
beginDrag(props, monitor, component) {
-
// 這里 return 出去的對象屬性自行選擇,這里只是用 id 作為演示
-
return { id: props.id }
-
}
-
-
endDrag(props, monitor, component) {
-
...
-
}
-
-
canDrag(props, monitor) {
-
...
-
}
-
-
isDragging(props, monitor) {
-
...
-
}
-
}
-
-
const collect = (connect, monitor) => ({
-
// 這里返回一個對象,會將對象的屬性都賦到組件的 props 中去。這些屬性需要自己定義。
-
connectDropTarget: connect.dropTarget(),
-
id: monitor.getItem().id
-
})
-
-
-
class MyComponent extends Component {
-
/* ... */
-
}
-
-
export default MyComponent;
-
復制代碼
參數講解:
- type: 必填。字符串,ES6符號或返回給定組件的函數props。只有為相同類型注冊的
drop targets
才會對此拖動源生成的項目做出反應 - spec:必填。一個普通的JavaScript對象,上面有一些允許的方法。它描述了拖動源如何對拖放事件做出反應。
- collect:必填。收集功能。它應該返回一個普通的對象注入你的組件。它接收兩個參數:connect和monitor。
- options:可選的。一個普通的對象。
spec 對象中的方法
-
beginDrag(props, monitor, component)
:必填。當拖動開始時,beginDrag
被調用。您必須返回描述被拖動數據的純JavaScript
對象。您返回的內容會被放置到monitor.getItem()
獲取到的對象中。 -
endDrag(props, monitor, component)
:可選的。當拖動停止時,endDrag
被調用。對於每個beginDrag
,endDrag
都會對應。 -
canDrag(props, monitor)
: 可選的。用它來指定當前是否允許拖動。如果您想要始終允許它,只需省略此方法即可。注意:您可能無法調用monitor.canDrag()
此方法。 -
isDragging(props, monitor)
: 可選的。默認情況下,僅啟動拖動操作的拖動源被視為拖動。注意:您可能無法調用monitor.isDragging()
此方法。
方法中的參數 props, monitor, component
props
:當前組件的props
monitor
:一個DragSourceMonitor
實例。使用它來查詢有關當前拖動狀態的信息,例如當前拖動的項目及其類型,當前和初始坐標和偏移,以及它是否已被刪除。component
:指定時,它是組件的實例。使用它來訪問底層DOM節點以進行位置或大小測量,或調用setState
以及其他組件方法。isDragging
、canDrag
方法里獲取不到component
這個參數,因為它們被調用時實例可能不可用
collect 中的 connect 和 monitor 參數
-
connect
: 一個DragSourceConnector
實例。它有兩種方法:dragPreview()和dragSource()。- dragSource() => (elementOrNode, options?):常用方法,返回一個函數,傳遞給組件用來將 source DOM 和 React DnD Backend 連接起來
- dragPreview():返回一個函數,傳遞給組件用來將拖動時預覽的 DOM 節點 和 React DnD Backend 連接起來
- dragSource() => (elementOrNode, options?):常用方法,返回一個函數,傳遞給組件用來將 source DOM 和 React DnD Backend 連接起來
-
monitor:一個
DragSourceMonitor
實例。包含下面各種方法:
方法 | 含義 |
---|---|
canDrag() |
是否可以被拖拽。如果沒有正在進行拖動操作,則返回 true |
isDragging() |
是否正在被拖動。如果正在進行拖動操作,則返回 true |
getItemType() |
返回標識當前拖動項的類型的字符串或ES6符號。 如果沒有拖動項目,則返回 null |
getItem() |
返回表示當前拖動項的普通對象。 每個拖動源都必須通過從其beginDrag()方法返回一個對象來指定它。 如果沒有拖動項目,則返回 null |
getDropResult() |
返回表示最后記錄的放置 drop result 對象 |
didDrop() |
如果某個 drop target 處理了 drop 事件,則返回 true,否則返回 false。即使 target 沒有返回 drop 結果,didDrop() 也會返回true。 在 endDrag() 中使用它來測試任何放置目標是否已處理掉落。 如果在 endDrag() 之外調用,則返回 false |
getInitialClientOffset() |
返回當前拖動操作開始時指針的{x,y} client 偏移量。 如果沒有拖動項目,則返回 null |
getInitialSourceClientOffset() |
返回當前拖動操作開始時 drag source 組件的根DOM節點的{x,y}client 偏移量。 如果沒有拖動項目,則返回 null |
getClientOffset() |
拖動操作正在進行時,返回指針的最后記錄的{x,y}client 偏移量。 如果沒有拖動項目,則返回 null |
getDifferenceFromInitialOffset() |
返回當前拖動操作開始時鼠標的最后記錄 client 偏移量與 client 偏移量之間的{x,y}差異。 如果沒有拖動項目,則返回 null |
getSourceClientOffset() |
返回 drag source 組件的根DOM節點的預計{x,y} client 偏移量,基於其在當前拖動操作開始時的位置以及移動差異。 如果沒有拖動項目,則返回 null |
三、DropTarget:使組件能夠放置拖拽組件
使用
DropTarget
包裹住組件,使其對拖動,懸停或 dropped 的兼容項目做出反應。
使用方式
-
import React, { Component } from 'react';
-
import { DropTarget } from 'react-dnd';
-
-
const spec = {
-
drop(props, monitor, component) {
-
// 這里 return 出去的對象屬性自行選擇,這里只是用 id 作為演示
-
return { id: props.id }
-
}
-
-
hover(props, monitor, component) {
-
...
-
}
-
-
canDrop(props, monitor) {
-
...
-
}
-
}
-
-
const collect = (connect, monitor) => ({
-
// 這里返回一個對象,會將對象的屬性都賦到組件的 props 中去。這些屬性需要自己定義。
-
connectDropTarget: connect.dropTarget()
-
})
-
-
-
class MyComponent extends Component {
-
/* ... */
-
}
-
export default MyComponent;
-
復制代碼
參數講解:
- type: 必填。字符串,ES6符號或返回給定組件的函數props。此放置目標僅對指定類型的
drag sources
項目做出反應 - spec:必填。一個普通的JavaScript對象,上面有一些允許的方法。它描述了放置目標如何對拖放事件做出反應。
- collect:必填。收集功能。它應該返回一個普通的道具對象注入你的組件。它接收兩個參數:connect 和 monitor。
- options:可選的。一個普通的對象。
spec 對象中的方法
-
drop(props, monitor, component)
: 可選的。在目標上放置兼容項目時調用。可以返回undefined
或普通對象。如果返回一個對象,它將成為放置結果,可以使用monitor.getDropResult()
獲取到。 -
hover(props, monitor, component)
: 可選的。當項目懸停在組件上時調用。您可以檢查monitor.isOver({ shallow: true })
以測試懸停是僅發生在當前目標上還是嵌套上。 -
canDrop(props, monitor)
: 可選的。使用它來指定放置目標是否能夠接受該項目。如果想要始終允許它,只需省略此方法即可。
文檔沒有提供按目的處理進入或離開事件的方法。而是
monitor.isOver()
從收集函數返回調用結果,以便我們可以使用componentDidUpdateReact
鈎子函數來處理組件中的進入和離開事件。
方法中的參數 props, monitor, component
props
:當前組件的props
monitor
:一個DropTargetMonitor
實例。使用它來查詢有關當前拖動狀態的信息,例如當前拖動的項目及其類型,當前和初始坐標和偏移,是否超過當前目標,以及是否可以刪除它。component
:指定時,它是組件的實例。使用它來訪問底層DOM節點以進行位置或大小測量,或調用setState
以及其他組件方法。canDrag
方法里獲取不到component
這個參數,因為它們被調用時實例可能不可用。
collect 中的 connect 和 monitor 參數
-
connect
: 一個DropTargetConnector
實例。它只有一種dropTarget()
方法。dropTarget() => (elementOrNode)
:常用方法,返回一個函數,傳遞給組件用來將 target DOM 和 React DnD Backend 連接起來。通過{ connectDropTarget: connect.dropTarget() }從收集函數返回,可以將任何React元素標記為可放置節點。
-
monitor:一個
DropTargetMonitor
實例。包含下面各種方法:
方法 | 含義 |
---|---|
canDrop() |
是否可以被放置。如果正在進行拖動操作,則返回true |
isOver(options) |
drag source 是否懸停在 drop target 區域。可以選擇傳遞{ shallow: true } 以嚴格檢查是否只有 drag source 懸停,而不是嵌套目標 |
getItemType() |
返回標識當前拖動項的類型的字符串或ES6符號。如果沒有拖動項目則返回 null |
getItem() |
返回表示當前拖動項的普通對象,每個拖動源都必須通過從其beginDrag()方法返回一個對象來指定它。如果沒有拖動項目則返回 null |
getDropResult() |
返回表示最后記錄的放置 drop result 對象 |
didDrop() |
如果某個 drop target 處理了 drop 事件,則返回 true,否則返回 false。即使 target 沒有返回 drop 結果,didDrop() 也會返回true。 在 endDrag() 中使用它來測試任何放置目標是否已處理掉落。 如果在 endDrag() 之外調用,則返回 false |
getInitialClientOffset() |
返回當前拖動操作開始時指針的{x,y} client 偏移量。 如果沒有拖動項目,則返回 null |
getInitialSourceClientOffset() |
返回當前拖動操作開始時 drag source 組件的根DOM節點的{x,y}client 偏移量。 如果沒有拖動項目,則返回 null |
getClientOffset() |
拖動操作正在進行時,返回指針的最后記錄的{x,y}client 偏移量。 如果沒有拖動項目,則返回 null |
getDifferenceFromInitialOffset() |
返回當前拖動操作開始時鼠標的最后記錄 client 偏移量與 client 偏移量之間的{x,y}差異。 如果沒有拖動項目,則返回 null |
getSourceClientOffset() |
返回 drag source 組件的根DOM節點的預計{x,y} client 偏移量,基於其在當前拖動操作開始時的位置以及移動差異。 如果沒有拖動項目,則返回 null |
四、DragDropContext & DragDropContextProvider
注意: 使用 DragSource 和 DropTarget 包裹的組件,必須放在: DragDropContext 包裹的根組件內部,或者 DragDropContextProvider 根標簽的內部。
DragDropContext
使用 DragDropContext
包裝應用程序的根組件以啟用 React DnD。
用法
-
import React, { Component } from 'react';
-
import HTML5Backend from 'react-dnd-html5-backend';
-
import { DragDropContext } from 'react-dnd';
-
-
-
class YourApp extends Component {
-
/* ... */
-
}
-
-
export default YourApp;
-
復制代碼
參數
-
backend:必填。一個 React DnD 后端。除非您正在編寫自定義的,否則建議使用 React DnD 附帶的 HTML5Backend。
-
context:backend 依賴。用於自定義后端的上下文對象。例如,HTML5Backend可以為iframe場景注入自定義窗口對象。
DragDropContextProvider
作為 DragDropContext
的替代方法,您可以使用 DragDropContextProvider
元素為應用程序啟用React DnD。與 DragDropContext
類似,這可以通過 backendprop
注入后端,但也可以注入一個 window
對象。
用法
-
import React, { Component } from 'react';
-
import HTML5Backend from 'react-dnd-html5-backend';
-
import { DragDropContextProvider } from 'react-dnd';
-
-
export default class YourApp extends Component {
-
render() {
-
return (
-
< DragDropContextProvider backend={HTML5Backend}>
-
/* ... */
-
</ DragDropContextProvider>
-
)
-
}
-
}
-
復制代碼
參數
-
backend:必填。一個 React DnD 后端。除非您正在編寫自定義的,否則建議使用 React DnD 附帶的 HTML5Backend。
-
context:backend 依賴。用於自定義后端的上下文對象。例如,HTML5Backend可以為iframe場景注入自定義窗口對象。
五、react-dnd 的簡單示例
本示例參照官方的 Dustbin 示例進行講解。
項目准備
當前項目使用 create-react-app
腳手架進行搭建,而且使用 react-dnd
時都是使用裝飾器語法進行編寫。所以需要先在項目里添加一些配置。
啟用裝飾器的配置方式可以參考我的上一篇文章:在 create-react-app 中啟用裝飾器語法。
新建 components
文件夾,用來存放編寫的組件。新建 types
文件夾,用來存放 type
字符串常量,在 types
目錄下創建 index.js
文件聲明對應的 type
值。
types/index.js
-
export default {
-
BOX: 'box'
-
}
-
復制代碼
所以當前項目 src
目錄下文件結構如下:
-
src
-
├── components/
-
├── types/
-
└── index.js
-
├── App.js
-
├── index.css
-
└── index.js
-
復制代碼
創建 Box 組件,作為 DragSource
在 components
目錄下,創建 Box.js
文件,編寫 Box
組件,使其可以進行拖動
components/Box.js
-
import React from 'react';
-
import PropTypes from 'prop-types';
-
import { DragSource } from 'react-dnd';
-
-
import ItemTypes from '../types';
-
-
const style = {
-
border: '1px dashed gray',
-
backgroundColor: 'white',
-
padding: '0.5rem 1rem',
-
marginRight: '1.5rem',
-
marginBottom: '1.5rem',
-
cursor: 'move',
-
float: 'left',
-
}
-
-
const boxSource = {
-
/**
-
* 開始拖拽時觸發當前函數
-
* @param {*} props 組件的 props
-
*/
-
beginDrag(props) {
-
// 返回的對象可以在 monitor.getItem() 中獲取到
-
return {
-
name: props.name,
-
}
-
},
-
-
/**
-
* 拖拽結束時觸發當前函數
-
* @param {*} props 當前組件的 props
-
* @param {*} monitor DragSourceMonitor 對象
-
*/
-
endDrag(props, monitor) {
-
// 當前拖拽的 item 組件
-
const item = monitor.getItem()
-
// 拖拽元素放下時,drop 結果
-
const dropResult = monitor.getDropResult()
-
-
// 如果 drop 結果存在,就彈出 alert 提示
-
if (dropResult) {
-
alert( `You dropped ${item.name} into ${dropResult.name}!`)
-
}
-
},
-
}
-
-
@DragSource(
-
// type 標識,這里是字符串 'box'
-
ItemTypes.BOX,
-
// 拖拽事件對象
-
boxSource,
-
// 收集功能函數,包含 connect 和 monitor 參數
-
// connect 里面的函數用來將 DOM 節點與 react-dnd 的 backend 建立聯系
-
(connect, monitor) => ({
-
// 包裹住 DOM 節點,使其可以進行拖拽操作
-
connectDragSource: connect.dragSource(),
-
// 是否處於拖拽狀態
-
isDragging: monitor.isDragging(),
-
}),
-
)
-
class Box extends React.Component {
-
-
static propTypes = {
-
name: PropTypes.string.isRequired,
-
isDragging: PropTypes.bool.isRequired,
-
connectDragSource: PropTypes.func.isRequired
-
}
-
-
render() {
-
const { isDragging, connectDragSource } = this.props
-
const { name } = this.props
-
const opacity = isDragging ? 0.4 : 1
-
-
// 使用 connectDragSource 包裹住 DOM 節點,使其可以接受各種拖動 API
-
// connectDragSource 包裹住的 DOM 節點才可以被拖動
-
return connectDragSource && connectDragSource(
-
<div style={{ ...style, opacity }}>
-
{name}
-
</div>
-
);
-
}
-
}
-
-
export default Box;
-
復制代碼
創建 Dustbin 組件,作為 DropTarget
在 components
目錄下,創建 Dustbin.js
文件,編寫 Dustbin
組件,使其可以接受對應的拖拽組件。
components/Dustbin.js
-
import React from 'react';
-
import PropTypes from 'prop-types';
-
-
import { DropTarget } from 'react-dnd';
-
import ItemTypes from '../types';
-
-
const style = {
-
height: '12rem',
-
width: '12rem',
-
marginRight: '1.5rem',
-
marginBottom: '1.5rem',
-
color: 'white',
-
padding: '1rem',
-
textAlign: 'center',
-
fontSize: '1rem',
-
lineHeight: 'normal',
-
float: 'left',
-
}
-
-
const boxTarget = {
-
// 當有對應的 drag source 放在當前組件區域時,會返回一個對象,可以在 monitor.getDropResult() 中獲取到
-
drop: () => ({ name: 'Dustbin' })
-
}
-
-
@DropTarget(
-
// type 標識,這里是字符串 'box'
-
ItemTypes.BOX,
-
// 接收拖拽的事件對象
-
boxTarget,
-
// 收集功能函數,包含 connect 和 monitor 參數
-
// connect 里面的函數用來將 DOM 節點與 react-dnd 的 backend 建立聯系
-
(connect, monitor) => ({
-
// 包裹住 DOM 節點,使其可以接收對應的拖拽組件
-
connectDropTarget: connect.dropTarget(),
-
// drag source是否在 drop target 區域
-
isOver: monitor.isOver(),
-
// 是否可以被放置
-
canDrop: monitor.canDrop(),
-
})
-
)
-
class Dustbin extends React.Component {
-
-
static propTypes = {
-
canDrop: PropTypes.bool.isRequired,
-
isOver: PropTypes.bool.isRequired,
-
connectDropTarget: PropTypes.func.isRequired
-
}
-
-
render() {
-
const { canDrop, isOver, connectDropTarget } = this.props;
-
const isActive = canDrop && isOver;
-
-
let backgroundColor = '#222';
-
// 拖拽組件此時正處於 drag target 區域時,當前組件背景色變為 darkgreen
-
if (isActive) {
-
backgroundColor = 'darkgreen';
-
}
-
// 當前組件可以放置 drag source 時,背景色變為 pink
-
else if (canDrop) {
-
backgroundColor = 'darkkhaki';
-
}
-
-
// 使用 connectDropTarget 包裹住 DOM 節點,使其可以接收對應的 drag source 組件
-
// connectDropTarget 包裹住的 DOM 節點才能接收 drag source 組件
-
return connectDropTarget && connectDropTarget(
-
<div style={{ ...style, backgroundColor }}>
-
{isActive ? 'Release to drop' : 'Drag a box here'}
-
</div>
-
);
-
}
-
}
-
-
export default Dustbin;
-
復制代碼
在 App.js 文件中使用 DragDropContext
App.js
-
import React, { Component } from 'react';
-
import { DragDropContext } from 'react-dnd';
-
import HTMLBackend from 'react-dnd-html5-backend';
-
-
import Dustbin from './components/Dustbin';
-
import Box from './components/Box';
-
-
// 將 HTMLBackend 作為參數傳給 DragDropContext
-
@DragDropContext(HTMLBackend)
-
class App extends Component {
-
render() {
-
return (
-
<div style={{ paddingLeft: 200, paddingTop: 50 }}>
-
<div style={{ overflow: 'hidden', clear: 'both' }}>
-
<Box name="Glass" />
-
<Box name="Banana" />
-
<Box name="Paper" />
-
</div>
-
<div style={{ overflow: 'hidden', clear: 'both' }}>
-
<Dustbin />
-
</div>
-
</div>
-
);
-
}
-
}
-
-
export default App;
-
復制代碼
運行項目,查看效果
運行項目:
-
$ npm run start
-
復制代碼
瀏覽器會自動打開 http://localhost:3000/
窗口,此時可以操作瀏覽器上的 Box 組件,結合項目代碼,查看效果。 預覽效果如下:
六、本文 Demo 地址
歡迎 Star!謝謝!
七、參考鏈接
react-dnd 官方文檔 拖拽組件:React DnD 的使用
作者:暖生
鏈接:https://juejin.im/post/5c92e7fc6fb9a070e5529322
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。