react-dnd 用法详解

本文详细讲解了 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
})
DragSource(type, spec, collect)
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 被调用。对于每个 beginDragendDrag 都会对应。

  • 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()。

    • 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

三、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

四、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场景注入自定义窗口对象。


五、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 }}&gt;
					{name}
				/div&gt;
			);
	}
}
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 放在当前组件区域时&#xff0c;会返回一个对象&#xff0c;可以在 monitor.getDropResult() 中获取到
	drop: ()  ({ name: Dustbin })
}
DropTarget(
	// type 标识&#xff0c;这里是字符串 box
	ItemTypes.BOX,
	// 接收拖拽的事件对象
	boxTarget,
	// 收集功能函数&#xff0c;包含 connect 和 monitor 参数
	// connect 里面的函数用来将 DOM 节点与 react-dnd 的 backend 建立联系
	(connect, monitor)  ({
		// 包裹住 DOM 节点&#xff0c;使其可以接收对应的拖拽组件
		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 &amp;&amp; isOver;
		let backgroundColor  #222;
		// 拖拽组件此时正处于 drag target 区域时&#xff0c;当前组件背景色变为 darkgreen
		if (isActive) {
			backgroundColor  darkgreen;
		} 
		// 当前组件可以放置 drag source 时&#xff0c;背景色变为 pink
		else if (canDrop) {
			backgroundColor  darkkhaki;
		}
		// 使用 connectDropTarget 包裹住 DOM 节点&#xff0c;使其可以接收对应的 drag source 组件
		// connectDropTarget 包裹住的 DOM 节点才能接收 drag source 组件
		return connectDropTarget &amp;&amp; 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 &#39;react&#39;;
import { DragDropContext } from &#39;react-dnd&#39;;
import HTMLBackend from &#39;react-dnd-html5-backend&#39;;
import Dustbin from &#39;./components/Dustbin&#39;;
import Box from &#39;./components/Box&#39;;
// 将 HTMLBackend 作为参数传给 DragDropContext
&#64;DragDropContext(HTMLBackend)
class App extends Component {
  render() {
    return (
        &lt;div style&#61;{<!-- -->{ paddingLeft: 200, paddingTop: 50 }}&gt;
            &lt;div style&#61;{<!-- -->{ overflow: &#39;hidden&#39;, clear: &#39;both&#39; }}&gt;
                &lt;Box name&#61;&#34;Glass&#34; /&gt;
                &lt;Box name&#61;&#34;Banana&#34; /&gt;
                &lt;Box name&#61;&#34;Paper&#34; /&gt;
            &lt;/div&gt;
            &lt;div style&#61;{<!-- -->{ overflow: &#39;hidden&#39;, clear: &#39;both&#39; }}&gt;
                &lt;Dustbin /&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    );
  }
}
export default App;
复制代码

运行项目,查看效果

运行项目:

$ npm run start 复制代码

浏览器会自动打开 http://localhost:3000/ 窗口,此时可以操作浏览器上的 Box 组件,结合项目代码,查看效果。 预览效果如下:

预览效果


六、本文 Demo 地址

react-dnd-dustbin

欢迎 Star!谢谢!


七、参考链接

react-dnd 官方文档 拖拽组件:React DnD 的使用


作者:暖生
链接:https://juejin.im/post/5c92e7fc6fb9a070e5529322
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值