概述
React DnD不同于大多数拖放库,如果你以前从未使用过它,它可能会让你感到害怕。然而,一旦你对其设计核心的一些概念有所了解,它就开始有意义了。我建议你在阅读其他文档之前先阅读这些概念。
其中一些概念类似于Flux和Redux架构。这不是一个巧合,因为React DnD内部使用Redux。
Items and Types
与Flux(或Redux)一样,React DnD使用数据而不是视图作为真实页面的来源。当你在屏幕上拖拽某个东西时,我们不会说一个组件或一个DOM节点正在被拖拽。相反,我们说某个type的item正在被拖动。
什么是item?item是一个简单的JavaScript对象,描述被拖动的内容。例如,在看板应用程序中,当你拖拽一张牌时,一个item可能是{ cardId: 42 }。在国际象棋游戏中,当你捡起一个棋子时,该item可能是{fromCell: 'C5',棋子:'queen'}。将拖拽的数据描述为普通对象可以帮助您保持组件的独立性,并且组件之间无关联。
那么什么是type呢?type是唯一标识应用程序中整个item类别的字符串(或符号)。在看板应用程序中,你可能有一个“card”type代表可拖动的卡片,而一个“list”type代表这些卡片的可拖动列表。在象棋中,你可能只有一种“piece”type。
type很有用,因为随着应用的增长,你可能想让更多的东西可拖放,但你不一定希望所有现有的拖放目标突然开始对新的item作出反应。这些types允许您指定哪些拖放源和拖放目标是兼容的。您可能会在应用程序中拥有类型常量的枚举,类似于您可能拥有Redux操作类型的枚举。
Monitors
拖放本身就是有状态的。拖动操作要么正在进行,要么没有进行。要么存在当前type和当前item,要么不存在。这个状态总得有个地方保存。
React DnD通过内部状态存储(称为Monitors上的几个小包装器向组件公开这个状态。Monitors允许您更新组件的props,以响应拖放状态的更改。
对于需要跟踪拖放状态的每个组件,您可以定义collecting function,从monitors中检索其中的相关部分。然后,React-DND负责及时调用collecting function并将其返回值合并到您的组件的props中。
假设你想在棋子被拖拽时高亮国际象棋的单元格。Cell组件的collecting function如下所示:
function collect(monitor) {
return {
highlighted: monitor.canDrop(),
hovered: monitor.isOver()
}
}
它表示React DnD将highlighted和hovered的最新值作为props传递给所有cell。
Connectors
如果后端处理DOM事件,但组件使用React来描述DOM,后端如何知道要监听哪些DOM节点?使用connectors。connectors允许您将一个预定义的角色(Drag Sources 、Drag preview或drop target)分配给render函数中的DOM节点。
实际上,connectors是作为第一个参数传递给collecting function的。我们看看如何用它来指定drop target:
function collect(connect, monitor) {
return {
highlighted: monitor.canDrop(),
hovered: monitor.isOver(),
connectDropTarget: connect.dropTarget()
}
}
在组件的render函数中,我们能够访问从monitor获得的数据,以及从connectors获得的功能:
render() {
const { highlighted, hovered, connectDropTarget } = this.props;
return connectDropTarget(
<div className={classSet({
'Cell': true,
'Cell--highlighted': highlighted,
'Cell--hovered': hovered
})}>
{this.props.children}
</div>
);
}
ConnectDroptargetCall会告诉React DND,即我们组件的根DOM节点是有效的drop target,并且其hover和drop事件应由后端处理。内部它通过将回调ref附加到您给出的react元素。connectors返回的函数是memoized的,因此它不会打断shouldComponentUpdate 优化。
Drag Sources and Drop Targets
drag sources 和 drop targets是React DnD的主要概念。它们将types、items、side effects和collecting function与绑定在组件上。
当你想让一个组件或它的某些部分可拖放时,你需要将该组件包装到一个drag source中。每个drag source都为item type注册,并且必须从组件的props中生成一个方法。它还可以指定其他方法来处理拖放事件。drag source还允许您为组件指定collecting function。
drop target与drag source相似。唯一的区别是,单个drop target可以同时注册多个item types,它可以处理hover或drop事件。\
Backends
(与移动端拖放相关)
Hooks vs 高阶组件
(。。。)
Hooks
有三种主要的Hooks可以将组件连接到React DnD中,第四个hooks可以连接到React DnD中用于测试或开发目的。
- useDrag
- useDrop
- useDragLayer
- useDragDropManager(dev/test hook)
Basic Example
要开始使用Hoos,让我们实现一个可拖动的box。
import { useDrag } from 'react-dnd'
function Box() {
const [{ isDragging }, drag, dragPreview] = useDrag(() => ({
// "type" is required. It is used by the "accept" specification of drop targets.
type: 'BOX',
// The collect function utilizes a "monitor" instance (see the Overview for what this is)
// to pull important pieces of state from the DnD system.
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
}))
return (
{/* This is optional. The dragPreview will be attached to the dragSource by default */}
<div ref={dragPreview} style={{ opacity: isDragging ? 0.5 : 1}}>
{/* The drag ref marks this node as being the "pick-up" node */}
<div role="Handle" ref={drag} />
</div>
)
}
现在,让我们实现可以拖拽进去的东西。
function Bucket() {
const [{ canDrop, isOver }, drop] = useDrop(() => ({
// The type (or types) to accept - strings or symbols
accept: 'BOX',
// Props to collect
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
})
}))
return (
<div
ref={drop}
role={'Dustbin'}
style={{ backgroundColor: isOver ? 'red' : 'white' }}
>
{canDrop ? 'Release to drop' : 'Drag a box here'}
</div>
)
}
useDrag
useDrag hook提供了一种将组件作为drag source连接到DnD的方法。通过向useDrag传递参数,您可以描述生成的可拖拽的types、表示drag source的item对象、collect的props等等。useDrag hooks返回的有:一组collected props,以及可以附加到drag source和drag preview元素上的refs。
import { useDrag } from 'react-dnd'
function DraggableComponent(props) {
const [collected, drag, dragPreview] = useDrag(() => ({
type,
item: { id }
}))
return collected.isDragging ? (
<div ref={dragPreview} />
) : (
<div ref={drag} {...collected}>
...
</div>
)
}
入参
| spec | 拖拽对象或创建拖拽对象的函数。拖拽对象的详细信息请参阅下面的内容。 |
|---|---|
| deps | 用于memoization的依赖数组,作用类似于useMemo的依赖数组。 |
返回值
| 【0】- connected props | 从collect函数返回的包含collected props的对象。如果没有定义collect函数,则返回一个空对象。 |
|---|---|
| 【1】- DragSource Ref | drag source的connector函数。必须附加到DOM的可拖动部分。 |
| 【2】- DragPreview Ref | drag preview的connector函数。可以附加到DOM的预览部分。 |
拖拽对象(入参中spec)的内容:
| required | 类型 | ||
|---|---|---|---|
| type | 是 | string或symbol | 只有相同类型drop target才会对该项作出反应。 |
| item | 是 | object或function | - 类型为object时,用于描述被拖动数据的普通JavaScript对象。这是关于drag source的唯一可用信息,所以选择需要的最小数据是很重要的。您可能会想在这里添加一些复杂的参数,但您应该尽量避免这样做,因为它会耦合drag source和drop target。建议使用类似{id}的对象。 |
- 类型为function时,它在拖动操作开始时被触发,并返回一个表示拖动操作的对象(参见第一个项目)。如果返回null,则取消拖动操作。 | | previewOptions | | object | 描述drag preview对象 | | options | | object | 可选择地包含下列属性的普通对象:- dropEffect: 用于表示拖放的拖放效果类型。(“move”或“copy”) | | end(item, monitor) | | function | 当拖动停止时被调用。对于每个begin的调用,保证有一个对应的end调用。您可以调用monitor.didDrop()来检查drop是否由对应的drop target执行。如果完成了drop,并且drop target通过drop()方法返回一个object来指定drop结果,则monitor.getDropResult()可用。这个方法是触发Flux操作的地方。 注意:如果组件在拖拽时卸载,component 参数将被设置为null。 | | canDrop(monitor) | | function | 指定当前组件是否可拖动。如果始终可拖动则忽略此方法。如果基于props上的参数词禁用拖放,那么可以使用它。注意:不能在该方法中调用monitor.canDrag()。 | | isDragging(monitor) | | function | 默认情况下,只有发起拖放操作的拖放源被认为是在拖放。你可以通过自定义isdraging方法来覆盖该方法。它可能会返回props.id === monitor.getItem().id,如果组件在拖拽过程中被卸载,然后用不同的父组件重新加载,则需要自定义isDragging。例如,当在看板的列表中移动一张卡片时,你希望这张卡片的外观保持不变,但是从技术层面上,每次移动到另一个列表时,卡片组件会被卸载,然后会挂载一个不同的组件。注意:不能在该方法中调用monitor.isDragging()。 | | collect | | function | collecting函数。返回props对象注入到组件中。它接收两个参数,monitor和props。请阅概述以了解监视器和收集功能的介绍。 |
useDrop
useDrop hook将drop target元素和DnD连接起来。通过将规范的拖拽对象作为入参传入useDrop,你可以定义drop target接受的types,collect处理的props,等。该函数返回一个数组,其中包含一个要附加到Drop Target节点的ref和collected的props。
import { useDrop } from 'react-dnd'
function myDropTarget(props) {
const [collectedProps, drop] = useDrop(() => ({
accept
}))
return <div ref={drop}>Drop Target</div>
}
入参
| spec | 拖拽对象或创建拖拽对象的函数。拖拽对象的详细信息请参阅下面的内容。 |
|---|---|
| deps | 用于memoization的依赖数组,作用类似于useMemo的依赖数组。 |
返回值
| 【0】- connected props | 从collect函数返回的包含collected props的对象。如果没有定义collect函数,则返回一个空对象。 |
|---|---|
| 【1】- DropDarget Ref | drop target的connector函数。必须附加到可放置的DOM元素。 |
拖拽对象(入参中spec)的内容:
| required | 类型 | ||||
|---|---|---|---|---|---|
| accept | 是 | string | symbol,Array<string | symbol> | 该drop target只对accept指定types的拖拽源作出反应。 |
| options | object | 如果你的组件的一些props不是标量(不是原始值或函数),在options中指定一个自定义的arePropsEqual(props, otherProps)函数可以提高性能。 | |||
| drop(item, monitor) | function | 在drop一个item时调用。返回undefined或对象。若返回对象,则在drag source的endDrag中的monitor.getDropResult()作为drop result使用。可用于根据接收到的拖放目标执行不同的操作。如果你有嵌套的drop target,你可以通过检查monitor.didDrop()和monitor.getDropResult()来测试嵌套的target是否已经drop。该方法和drag source的endDrag方法都是触发Flux动作的好地方。 如果定义了canDrop()并返回false,则不会调用此方法。 | |||
| hover(item, monitor) | function | 当item hover在组件上时被调用。可以使用monitor.isOver({ shallow: true })来检测hover发生在当前target上还是嵌套的target上。与drop()不同,即使定义了canDrop()并返回false,这个方法也会被调用。 | |||
| canDrop(monitor) | function | 指定drop target是否接受拖动源。如果始终接受则忽略此方法。如果基于props或monitor.getItem()上的某些参数词禁用拖放,那么可以使用它。注意:不能在该方法中调用monitor.canDrag()。 | |||
| collect | function | collecting函数。返回props对象注入到组件中。它接收两个参数,monitor和props。请阅概述以了解监视器和收集功能的介绍。 |
useDragLayer
useDragLayer hook将组件作为拖动层连接到DnD。
import { useDragLayer } from 'react-dnd'
function DragLayerComponent(props) {
const collectedProps = useDragLayer(
monitor => /* Collected Props */
)
return <div>...</div>
}
入参
| collect | required | collecting函数。返回props对象注入到组件中。接收monitor和 props两个参数 |
|---|
返回值
从collect函数中收集的属性的对象。
useDragDropManager
该hook为用户提供进入DnD系统的权限。DragDropManager实例是一个由React DnD创建的单例,包含了对状态、监视器、后台等的访问。
import { useDragDropManager } from 'react-dnd'
function Example() {
// The manager provides access to all of React DnD's internals
const dragDropManager = useDragDropManager()
return <div>Example</div>
}