React-DnD官方文档中文版

8,131 阅读10分钟

概述

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中用于测试或开发目的。

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 Refdrag source的connector函数。必须附加到DOM的可拖动部分。
【2】- DragPreview Refdrag preview的connector函数。可以附加到DOM的预览部分。

拖拽对象(入参中spec)的内容:

required类型
typestring或symbol只有相同类型drop target才会对该项作出反应。
itemobject或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 Refdrop target的connector函数。必须附加到可放置的DOM元素。

拖拽对象(入参中spec)的内容:

required类型
acceptstringsymbol,Array<stringsymbol>该drop target只对accept指定types的拖拽源作出反应。
optionsobject如果你的组件的一些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()。
collectfunctioncollecting函数。返回props对象注入到组件中。它接收两个参数,monitor和props。请阅概述以了解监视器和收集功能的介绍。

useDragLayer

useDragLayer hook将组件作为拖动层连接到DnD。

import { useDragLayer } from 'react-dnd'

function DragLayerComponent(props) {
  const collectedProps = useDragLayer(
    monitor => /* Collected Props */
  )
  return <div>...</div>
}

入参

collectrequiredcollecting函数。返回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>
}