本文詳細講解了 react-dnd 的 API 以及用法,而且附上了可供參考的 Demo,但願可以給須要的朋友提供一下幫助。css
React DnD 是一組 React 高階組件,使用的時候只須要使用對應的 API 將目標組件進行包裹,便可實現拖動或接受拖動元素的功能。將拖動的事件轉換成對象中對應狀態的形式,不須要開發者本身判斷拖動狀態,只須要在傳入的 spec 對象中各個狀態屬性中作對應處理便可。剛剛接觸可能難以理解,真正熟悉用法以後會感受很方便。html
本文
Demo
地址:react-dnd-dustbin。若有幫助,歡迎 Star。html5
使用
DragSource
包裹住組件,使其能夠進行拖動。react
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
})
@DragSource(type, spec, collect)
class MyComponent extends Component {
/* ... */
}
export default MyComponent;
複製代碼
參數講解:git
drop targets
纔會對此拖動源生成的項目作出反應beginDrag(props, monitor, component)
:必填。當拖動開始時,beginDrag
被調用。您必須返回描述被拖動數據的純 JavaScript
對象。您返回的內容會被放置到 monitor.getItem()
獲取到的對象中。github
endDrag(props, monitor, component)
:可選的。當拖動中止時,endDrag
被調用。對於每一個 beginDrag
,endDrag
都會對應。npm
canDrag(props, monitor)
: 可選的。用它來指定當前是否容許拖動。若是您想要始終容許它,只需省略此方法便可。注意:您可能沒法調用monitor.canDrag()
此方法。後端
isDragging(props, monitor)
: 可選的。默認狀況下,僅啓動拖動操做的拖動源被視爲拖動。注意:您可能沒法調用 monitor.isDragging()
此方法。瀏覽器
props
:當前組件的 props
monitor
:一個 DragSourceMonitor
實例。使用它來查詢有關當前拖動狀態的信息,例如當前拖動的項目及其類型,當前和初始座標和偏移,以及它是否已被刪除。component
:指定時,它是組件的實例。使用它來訪問底層DOM節點以進行位置或大小測量,或調用 setState
以及其餘組件方法。isDragging
、 canDrag
方法裏獲取不到 component
這個參數,由於它們被調用時實例可能不可用connect
: 一個 DragSourceConnector
實例。它有兩種方法:dragPreview()和dragSource()。bash
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
包裹住組件,使其對拖動,懸停或 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()
})
@DropTarget(type, spec, collect)
class MyComponent extends Component {
/* ... */
}
export default MyComponent;
複製代碼
參數講解:
drag sources
項目作出反應drop(props, monitor, component)
: 可選的。在目標上放置兼容項目時調用。能夠返回 undefined
或普通對象。若是返回一個對象,它將成爲放置結果,可使用 monitor.getDropResult()
獲取到。
hover(props, monitor, component)
: 可選的。當項目懸停在組件上時調用。您能夠檢查 monitor.isOver({ shallow: true })
以測試懸停是僅發生在當前目標上仍是嵌套上。
canDrop(props, monitor)
: 可選的。使用它來指定放置目標是否可以接受該項目。若是想要始終容許它,只需省略此方法便可。
文檔沒有提供按目的處理進入或離開事件的方法。而是
monitor.isOver()
從收集函數返回調用結果,以便咱們可使用componentDidUpdateReact
鉤子函數來處理組件中的進入和離開事件。
props
:當前組件的 props
monitor
:一個 DropTargetMonitor
實例。使用它來查詢有關當前拖動狀態的信息,例如當前拖動的項目及其類型,當前和初始座標和偏移,是否超過當前目標,以及是否能夠刪除它。component
:指定時,它是組件的實例。使用它來訪問底層DOM節點以進行位置或大小測量,或調用 setState
以及其餘組件方法。canDrag
方法裏獲取不到 component
這個參數,由於它們被調用時實例可能不可用。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 |
注意: 使用 DragSource 和 DropTarget 包裹的組件,必須放在: DragDropContext 包裹的根組件內部,或者 DragDropContextProvider 根標籤的內部。
使用 DragDropContext
包裝應用程序的根組件以啓用 React DnD。
import React, { Component } from 'react';
import HTML5Backend from 'react-dnd-html5-backend';
import { DragDropContext } from 'react-dnd';
@DragDropContext(HTML5Backend)
class YourApp extends Component {
/* ... */
}
export default YourApp;
複製代碼
backend:必填。一個 React DnD 後端。除非您正在編寫自定義的,不然建議使用 React DnD 附帶的 HTML5Backend。
context:backend 依賴。用於自定義後端的上下文對象。例如,HTML5Backend能夠爲iframe場景注入自定義窗口對象。
做爲 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場景注入自定義窗口對象。
本示例參照官方的 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
複製代碼
在 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;
複製代碼
在 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
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 組件,結合項目代碼,查看效果。 預覽效果以下:
歡迎 Star!謝謝!