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 }}>
					{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 地址

react-dnd-dustbin

欢迎 Star!谢谢!


七、参考链接

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


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

  • 3
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
react-dnd 是一个用于拖放操作的 React 库。它提供了一些组件和工具,使我们能够轻松地将拖动操作集成到我们的 React 应用中。 要使用 react-dnd,我们需要先安装它。可以使用 npm 命令行工具完成安装: ``` npm install --save react-dnd react-dnd-html5-backend ``` 我们需要安装 react-dndreact-dnd-html5-backend。后者是一个 HTML5 后端,它提供了一些基本的本地浏览器支持。 然后,我们需要在我们的应用程序中导入 react-dnd 的相关组件和工具。通常,我们需要导入 DragSource、DropTarget、DragDropContext 和 HTML5Backend: ``` import { DragSource, DropTarget } from 'react-dnd'; import HTML5Backend from 'react-dnd-html5-backend'; import { DragDropContext } from 'react-dnd'; ``` 接下来,我们需要创建一个 DragSource 和一个 DropTarget 组件。这些组件是我们用来定义拖放操作的核心。 DragSource 定义一个组件可以被拖动的方式,DropTarget 定义一个组件可以接收拖放的元素。 例如,创建一个简单的 DragSource 组件会像这样: ``` import { DragSource } from 'react-dnd'; const ItemTypes = { CARD: 'card' }; const cardSource = { beginDrag(props) { return { id: props.id }; } }; function collect(connect, monitor) { return { connectDragSource: connect.dragSource(), isDragging: monitor.isDragging() }; } const Card = ( <div> Drag Me! </div> ); export default DragSource(ItemTypes.CARD, cardSource, collect)(Card); ``` 这个简单的例子定义了一个拖动的卡片元素,其中 cardSource 是定义拖动行为的 Javascript 对象,beginDrag 方法返回了一个包含 id 属性的拖动项,用于标识当前拖动的卡片。 接下来,我们需要使用 DragDropContext 组件来包裹整个应用程序,并使用 HTML5Backend 作为拖放后端: ``` import { DragDropContext } from 'react-dnd'; import HTML5Backend from 'react-dnd-html5-backend'; ... export default DragDropContext(HTML5Backend)(App); ``` 这样我们就可以在应用程序中使用 DragSource 和 DropTarget 组件了。 当我们成功地拖动一个拖动源到它的目标时,它会触发 DropTarget 上的 drop 方法,我们可以在这个方法中定义我们想要发生的操作,例如重新排列、合并或删除元素。 以上就是 react-dnd 的简单使用方法。具体的实现还需要根据实际需求进行详细的设计和开发。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值