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
-
})
-
-
-
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
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。