react-dnd 用法詳解

本文詳細講解了 react-dnd 的 API 以及用法,而且附上了可供參考的 Demo,但願可以給須要的朋友提供一下幫助。css


1、概念

React DnD 是一組 React 高階組件,使用的時候只須要使用對應的 API 將目標組件進行包裹,便可實現拖動或接受拖動元素的功能。將拖動的事件轉換成對象中對應狀態的形式,不須要開發者本身判斷拖動狀態,只須要在傳入的 spec 對象中各個狀態屬性中作對應處理便可。剛剛接觸可能難以理解,真正熟悉用法以後會感受很方便。html

本文 Demo 地址:react-dnd-dustbin。若有幫助,歡迎 Star。html5


2、DragSource:使組件可以被拖拽

使用 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

  • type: 必填。字符串,ES6符號或返回給定組件的函數props。只有爲相同類型註冊的 drop targets 纔會對此拖動源生成的項目作出反應
  • spec:必填。一個普通的JavaScript對象,上面有一些容許的方法。它描述了拖動源如何對拖放事件作出反應。
  • collect:必填。收集功能。它應該返回一個普通的對象注入你的組件。它接收兩個參數:connect和monitor。
  • options:可選的。一個普通的對象。

spec 對象中的方法

  • beginDrag(props, monitor, component):必填。當拖動開始時,beginDrag 被調用。您必須返回描述被拖動數據的純 JavaScript 對象。您返回的內容會被放置到 monitor.getItem() 獲取到的對象中。github

  • endDrag(props, monitor, component):可選的。當拖動中止時,endDrag 被調用。對於每一個 beginDragendDrag 都會對應。npm

  • canDrag(props, monitor): 可選的。用它來指定當前是否容許拖動。若是您想要始終容許它,只需省略此方法便可。注意:您可能沒法調用monitor.canDrag() 此方法。後端

  • isDragging(props, monitor): 可選的。默認狀況下,僅啓動拖動操做的拖動源被視爲拖動。注意:您可能沒法調用 monitor.isDragging() 此方法。瀏覽器

方法中的參數 props, monitor, component

  • props:當前組件的 props
  • monitor:一個 DragSourceMonitor 實例。使用它來查詢有關當前拖動狀態的信息,例如當前拖動的項目及其類型,當前和初始座標和偏移,以及它是否已被刪除。
  • component:指定時,它是組件的實例。使用它來訪問底層DOM節點以進行位置或大小測量,或調用 setState 以及其餘組件方法。isDraggingcanDrag 方法裏獲取不到 component 這個參數,由於它們被調用時實例可能不可用

collect 中的 connect 和 monitor 參數

  • connect: 一個 DragSourceConnector 實例。它有兩種方法:dragPreview()和dragSource()。bash

    • dragSource() => (elementOrNode, options?):經常使用方法,返回一個函數,傳遞給組件用來將 source DOM 和 React DnD Backend 鏈接起來
      • dragPreview():返回一個函數,傳遞給組件用來將拖動時預覽的 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

3、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()
})

@DropTarget(type, spec, collect)
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

4、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';

@DragDropContext(HTML5Backend)
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場景注入自定義窗口對象。


5、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 組件,結合項目代碼,查看效果。 預覽效果以下:

預覽效果


6、本文 Demo 地址

react-dnd-dustbin

歡迎 Star!謝謝!


7、參考連接

react-dnd 官方文檔 拖拽組件:React DnD 的使用

相關文章
相關標籤/搜索