React React生态-拖拽组件React-DnD

3,028 阅读13分钟

React-DnD

React-DnD是一组可帮助用户构建复杂的拖放界面,同时保持组件解耦的React实用程序。在应用程序的不同部分之间通过拖动传输数据,并且组件会更改其外观和应用程序状态以响应拖放事件。

译自官网,并补充了相关内容,加深理解

【要点解析】

React-DnD的几个重要API:DndProvideruseDraguseDrop

  • useDrag表示拖动源,useDrop表示放置源。要使二者接入同一个Dnd系统,需将二者包在DndProvider里面,并且要指定useDrag的type和useDrop的accept为相同的值(类型为string或symbol)
  • 将useDrag的第二个返回值(dragRef)设置为拖动源的ref
  • 将useDrop的第二个返回值(dropRef)设置为放置源的ref
  • 在Specification Object Members中的collect方法中做一些数据处理

概述

Item项目和Type类型

  • React-DnD使用数据而不是视图作为事实上的的资源。当在屏幕上拖动某个东西时,不会说某个组件或DOM节点正在被拖动。相反,会说某个特定类型的item项目正在被拖动。
  • Item 项目:用来描述被拖动的内容,是可用于放置目标的有关拖动源的唯一信息。将被拖动的数据描述为一个普通对象可以帮助保持组件的解藕和相互之间的无察觉
  • type 类型:是唯一标识应用程序中整个类型的字符串/符号。

Monitor监视器

  • 拖拽本质上是有状态的,要么正在拖动,要么没有;要么有确定的当前类型和当前项,要么没有。其状态必须存在于某个地方。
  • React-DnD通过内部状态存储器(称为监视器)上的一些小包装器将这种状态暴露给组件。监视器允许用户更新组件的props以响应拖放状态的更改。
  • 对于需要跟踪拖放状态的每个组件,可以定义一个collect函数,从监视器中取出拖放状态的相关数据。Dnd负责及时调用collect函数,并将其返回值合并到组件的props中。
// DnD将最新的返回值对象传递给所有的Cell实例作为props
function collect(monitor) {
    return {
        hovered: monitor.isOver()
    }
}

Connectors连接器

  • 连接器允许将一个预定义角色(拖动源、拖动预览或拖放目标)分配给render函数中的DOM节点。
  • 在组件的渲染方法中,可以访问到monitor获得的数据connect获得的函数(即collect函数中的)
  • 在内部,它的工作方式是将一个回调引用ref附加到给它的React元素上。连接器返回的函数是memoized的,所以它不会破坏shouldComponentUpdate逻辑
// connectDropTarget告诉DnD组件的root DOM是一个有效的放置目标
function collect(connect, monitor) {
    return {
        hovered: monitor.isOver(),
        connectDropTarget: connect.dropTarget()
    }
}

Hooks和高阶组件

  • React-DnD提供钩子,将组件连接到DnD引擎,并允许用户收集呈现的监视器状态。
  • 拖放交互本质上是有状态的,前三个主要的钩子用于将组件连接到React-DnD中。

  • useDrag

  • useDrop
  • useDragLayer
  • useDragDropManager(dev/test)
  • Drag SourcesDrop Targets可以理解为React-DnD的基本的抽象单元。其将types、items、side effects和collect函数一起绑定到组件中。
const BoxContent = () => {
  const [{isDragging}, dragRef, dragPriview] = useDrag(() => {
    // 必填,对应于useDrop的accept,string或symbol
    type: 'testType',
    collect: (monitor) => ({
      isDragging: monitor.isDragging()
    })
  })
  
  return (
    // dragPriview会被默认附加到dragSource;
    // dragRef标记其节点为pick-up节点
    <div ref={dragPriview}>
      <div role="Handle" ref={dragRef}></div>
    </div>
  )
}

const Bucket = () => {
  const [{canDrop, isOver}, dropRef] = useDrop(() => {
    // 必填,string或symbol
    accept: 'testType',
    collect: (monitor) => ({
      isOver: monitor.isOver(),
      canDrop: monitor.canDrop()
    })
  })

  return (
    <div ref={dropRef} role="Dustbin">
      { canDrop ? '请释放' : '请拖动到这里' }
    </div>
  )
}

组件

DndProvider 注入

为应用程序提供React-DnD功能

  • API

  • backend: 必填,HTML5Backend/TouchBackend/自定义

  • context: 选填,用户配置后端的上下文
  • options: 选填,用于配置后端的选项对象,自定义时可传入backend

Hooks

useDrag 声明拖动源

用于将当前组件用作拖动源(drag source) 的钩子。

提供了一种将组件作为拖动源连接到DnD系统的方法。用户可以声明性地描述可拖动生成的type、表示拖动源的item对象、collect收集的props等等。

返回一些关键item:

  • 一组collect props
  • 可以附加到拖动源的ref引用
  • 可以附加到拖动预览元素的ref引用
function DraggableComponent(props) {
  const [collected, dragRef, dragPreviewRef] = useDrag(() => ({
    type,
    item: { id },
    collect: (monitor) => ({
      isDragging: monitor.isDragging()
    })
  }))
  return collected.isDragging ? (
    <div ref={ dragPreviewRef } />
  ) : (
    <div ref={ dragRef } {...collected}>
      ...
    </div>
  )
}

入参

参数描述
spec创建一个规范对象的规范对象或函数
deps依赖数组。包含规范对象的规范数组,行为类似于useMemo

返回参数

参数描述
[0] - Collected Props一个包含从collect函数收集的属性的对象。若collect函数未定义,则返回空对象
[1] - DragSource Ref一个拖动源的connector连接器函数。其必须附加到DOM的可拖动部分
[2] - DragPreview Ref一个拖动预览的connector连接器函数。可附加到DOM的预览部分

规范对象成员

属性必填否描述
type必填,string/symbol唯一标识应用程序中整个类型的字符串/符号。只有注册为相同类型的拖放目标才会对此项目做出反应。对应useDrop的accept。
item必填,object/functionitem为对象时,用来描述被拖动数据的普通对象,是对拖放目标而言关于拖动源的唯一可用的信息。选择他们需要知道的最少的数据是很重要的,用户可能很想在这里放置一个复杂的引用,但是应该尽量避免这样做,因为它将拖动源和拖放目标绑定在一起。拖动源的信息可以提供给拖放目标,所以选择拖动源非常重要,将被拖动的数据描述为一个对象可以帮助保持组件的解藕和相互之间的无察觉。item为函数时,它在拖动操作开始时触发,并返回一个表示拖动操作的对象;若取消拖动操作则返回null。
previewOptions描述拖动预览选项的普通对象。
options选择性地包含如下属性的普通对象- dropEffect:可选,使用在该拖动上的拖放效果类型(move/copy)
begin(monitor)拖动操作开始时触发。毋需返回任何内容,但若返回对象,其将覆盖item规范的默认属性。
end(item, monitor)拖动停止的时候触发。
canDrag(monitor)指定当前是否允许被拖动(默认允许)。
isDragging(monitor)只有启动拖动操作的拖动源才被视为拖动(默认情况下)。
collect收集函数。返回一个注入到组件中的props对象,接收两个参数(monitor和 props)

useDrop 声明拖放目标

用于将当前组件作为拖放目标(drop target) 的钩子。

将组件作为拖放目标连接到DnD系统中。可以指定拖放目标要接收的目标数据类型和要收集的props等。此hook返回一个数组,其中包含要附加到拖放目标节点的引用ref和collect props。

function myDropTarget(props) {
  const [collectedProps, dropRef] = useDrop(() => ({
    accept
  }))

  return <div ref={ dropRef }>Drop Target</div>
}

入参

参数描述
spec创建一个规范对象的规范对象或函数
deps依赖数组。包含规范对象的规范数组,行为类似于useMemo

返回参数

参数描述
[0] - Collected Props一个普通js对象,其中包含从collect函数收集的属性。若collect未定义函数,则返回空对象
[1] - DropTarget Ref拖放目标的连接器函数。其必须附加到DOM的拖放目标部分

传入参数

属性必填否描述
accept必填,string/symbol/array此放置目标将仅对由指定类型的拖动源产生的项目做出反应。对应useDrag的type。
options一个普通对象。若组件的某些props不是标量的(即不是原始值或函数),则在options对象内部指定自定义的arePropsEqual(props, otherProps)函数,以提高性能。
drop(item, monitor)当对应item拖放到目标上时调用,可以返回undefined或纯对象。如果返回一个对象,它将成为拖放结果;并且对于拖动源来讲,拖动结束方法monitor.getDropResult()变得可用。用户可以根据拖放目标接收到的target执行不同的操作。如果用户有嵌套的drop目标,则可以通过monitor.didDrop()和monitor.getDropResult()来测试嵌套目标是否已经处理。如果canDrop()已定义并返回false,则不会调用此方法。
hover(item, monitor)当项目悬停在组件上时调用。用户可以检查monitor.isOver({ shallow: true })测试悬停是仅发生在当前的目标,还是发生在嵌套目标上。与drop()方法不同的是,即使canDrop()已定义并返回false,该方法也将被调用。用户可以检查monitor.canDrop()测试是否是这种情况。
canDrop(item, monitor)指定拖放目标是否能够接受item,若要始终允许它,则只需忽略该方法。
collect收集函数。返回一个注入到组件中的props对象。接收两个参数(monitor和 props)

useDragLayer 拖拽图层

允许将一个组件作为拖动图层连接到Dnd系统中。

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

参数

参数描述
collect必填,收集函数。返回一个注入到组件中的props对象。接收两个参数(monitor和 props)

返回值: 从 collect 函数中收集属性的对象

useDragDropManager 拖拽管理器

该钩子为用户提供了进入DnD系统的访问权限。

其实例是React-DnD创建的单例模式,包含对状态、监视器、后端等的访问。

function Example() {
  // The manager provides access to all of React DnD's internals
  const dragDropManager = useDragDropManager()

  return <div>Example</div>

}

Monitor State

dragSourceMonitor

一个传递给拖动源的收集函数的对象。

用户可以获得指定拖动源的拖动状态的信息,绑定到该监视器的特定拖动源称之为监视器的所有者。

methods

canDrag()是否可以拖拽。当前无拖拽操作进行返回true
isDragging()是否在拖拽。
getItemType()返回标识当前拖动项类型的字符串或符号。如果未拖动任何项,则返回null。
getItem()返回当前拖动项的普通对象。每个拖动源必须通过其 beginDrag ()方法返回一个对象来指定它。如果未拖动任何项,则返回null。
getDropResult()返回一个表示最近记录的删除结果的普通对象。The drop targets may optionally specify it by returning an object from their drop()methods. When a chain of drop()is dispatched to the nested targets, bottom up, any parent that explicitly returns its own result from drop()overrides the child's drop result previously set by the child. Returns nullif called outside endDrag()
didDrop()如果某个拖放目标已经处理了拖放事件,返回true,否则false。即使目标没有返回删除结果,也会返回 true。在 endDrag()中使用它来测试是否有任何拖放目标已经处理了拖放。
getInitialClientOffset()返回当前拖动操作开始时指针的{ x,y }偏移量。如果未拖动任何项,则返回null。
getInitialSourceClientOffset()返回当前拖动操作开始时拖动源组件的根DOM节点的{ x,y }偏移量。如果未拖动任何项,则返回null。
getClientOffset()当拖动操作进行时,返回指针最后记录的{ x,y }偏移量,如果未拖动任何项,则返回null。
getDifferenceFromInitialOffset()当前拖动操作启动时,返回指针最后记录的偏移量与偏移量之间的{ x,y }差值。如果未拖动任何项,则返回null。
getSourceClientOffset()根据当前拖动操作开始时的位置和移动差异,返回拖动源组件的根DOM节点的预计{ x,y }偏移量。如果未拖动任何项,则返回null。

dropTargetMonitor

传递给拖放目标的收集函数的对象。它的方法使您可以获得有关特定拖放目标的拖动状态的信息。绑定到该监视器的特定拖放目标在下面称为监视器的所有者。

methods

canDrop()当前无拖动操作发生,并且方法的拥有者返回true或未定义时,返回true。
isOver(options)如果有拖动操作正在进行,并且指针当前悬停在所有者上方,则返回true。
getItemType()返回标识当前拖动项类型的字符串或符号。如果未拖动任何项,则返回null。
getItem()返回表示当前拖动项的普通对象。每个拖动源必须通过从其 beginDrag ()方法返回一个对象来指定它。如果未拖动任何项,则返回null。
getDropResult()返回一个表示最近记录的删除结果的普通对象。拖放目标可以通过从其 drop ()方法返回一个对象来选择性地指定它。当为嵌套目标自底向上分派 drop ()链时,任何显式返回其自己的 drop ()结果的父类都会覆盖以前由子类设置的子拖放结果。
didDrop()如果某个拖放目标已经处理了拖放事件,返回true,否则false。即使目标没有返回删除结果,也会返回 true。在 endDrag()中使用它来测试是否有任何拖放目标已经处理了拖放。若在外部调用 endDrag()则返回false。
getInitialClientOffset()返回当前拖动操作开始时指针的{ x,y }偏移量。如果未拖动任何项,则返回null。
getInitialSourceClientOffset()返回当前拖动操作开始时拖动源组件的根DOM节点的{ x,y }偏移量。如果未拖动任何项,则返回null。
getClientOffset()当拖动操作进行时,返回指针最后记录的{ x,y }偏移量,如果未拖动任何项,则返回null。
getDifferenceFromInitialOffset()当前拖动操作启动时,返回指针最后记录的偏移量与偏移量之间的{ x,y }差值。如果未拖动任何项,则返回null。
getSourceClientOffset()根据当前拖动操作开始时的位置和移动差异,返回拖动源组件的根DOM节点的预计{ x,y }偏移量。如果未拖动任何项,则返回null。

dragLayerMonitor

一个传递给拖拽层的收集函数的对象。它的方法使您可以获得有关全局拖动状态的信息。

methods

isDragging()是否在拖拽。
getItemType()返回标识当前拖动项类型的字符串或符号。如果未拖动任何项,则返回null。
getItem()返回当前拖动项的普通对象。每个拖动源必须通过其 beginDrag ()方法返回一个对象来指定它。如果未拖动任何项,则返回null。
getInitialClientOffset()返回当前拖动操作开始时指针的{ x,y }偏移量。如果未拖动任何项,则返回null。
getInitialSourceClientOffset()返回当前拖动操作开始时拖动源组件的根DOM节点的{ x,y }偏移量。如果未拖动任何项,则返回null。
getClientOffset()当拖动操作进行时,返回指针最后记录的{ x,y }偏移量,如果未拖动任何项,则返回null。
getDifferenceFromInitialOffset()当前拖动操作启动时,返回指针最后记录的偏移量与偏移量之间的{ x,y }差值。如果未拖动任何项,则返回null。
getSourceClientOffset()根据当前拖动操作开始时的位置和移动差异,返回拖动源组件的根DOM节点的预计{ x,y }偏移量。如果未拖动任何项,则返回null。