前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于react-dnd,看这一篇就够了

关于react-dnd,看这一篇就够了

作者头像
Jou
发布2022-10-28 10:54:07
14.4K1
发布2022-10-28 10:54:07
举报
文章被收录于专栏:前端技术归纳前端技术归纳

theme: channing-cyan

前言

最近公司准备开发一个审批流系统,其中会用到拖拽工具来搭建流程,关于拖拽的实现我们选择了react-dnd这个库,本文总结了react-dnd API的详细用法,并附上不同场景的demo,希望对大家有用。

概念

React DnD 是一组 React 高阶组件,使用的时候只需要使用对应的 API 将目标组件进行包裹,即可实现拖动或接受拖动元素的功能。

在拖动的过程中,不需要开发者自己判断拖动状态,只需要在传入的 spec 对象中各个状态属性中做对应处理即可,因为react-dnd使用了redux管理自身内部的状态。

Some of these concepts resemble the Flux and Redux architectures. This is not a coincidence, as React DnD uses Redux internally.

值得注意的是,react-dnd并不会改变页面的视图,它只会改变页面元素的数据流向,因此它所提供的拖拽效果并不是很炫酷的,我们可能需要写额外的视图层来完成想要的效果,但是这种拖拽管理方式非常的通用,可以在任何场景下使用,非常适合用来定制。

react-dnd文档传送门

核心API

介绍实现拖拽和数据流转的核心API,这里以Hook为例。

DndProvider

如果想要使用 React DnD,首先需要在外层元素上加一个 DndProvider。

代码语言:javascript
复制
import { HTML5Backend } from 'react-dnd-html5-backend';
import { DndProvider } from 'react-dnd';

<DndProvider backend={HTML5Backend}>
    <TutorialApp />
</DndProvider>

DndProvider 的本质是一个由 React.createContext 创建一个上下文的容器(组件),用于控制拖拽的行为,数据的共享,类似于react-redux的Provider。

Backend

上面我们给DndProvider传的参数是一个backend,那么这里来解释一下什么是backend

React DnD 将 DOM 事件相关的代码独立出来,将拖拽事件转换为 React DnD 内部的 redux action。由于拖拽发生在 H5 的时候是 ondrag,发生在移动设备的时候是由 touch 模拟,React DnD 将这部分单独抽出来,方便后续的扩展,这部分就叫做 Backend。它是 DnD 在 Dom 层的实现。

  • react-dnd-html5-backend : 用于控制html5事件的backend
  • react-dnd-touch-backend : 用于控制移动端touch事件的backend
  • react-dnd-test-backend : 用户可以参考自定义backend

useDrag

让DOM实现拖拽能力的构子,官方用例如下

代码语言:javascript
复制
import { DragPreviewImage, useDrag } from 'react-dnd';

export const Knight: FC = () => {
    const [{ isDragging }, drag, preview] = useDrag(
        () => ({
            type: ItemTypes.KNIGHT,
            collect: (monitor) => ({
                isDragging: !!monitor.isDragging()
            })
        }),
        []
    );

    return (
        <>
            <DragPreviewImage connect={preview} src={knightImage} />
            <div
                ref={drag}
            >
                ♘
            </div>
        </>
    );
};
useDrag返回三个参数

第一个返回值是一个对象 表示关联在拖拽过程中的变量,需要在传入useDrag的规范方法的collect属性中进行映射绑定,比如:isDraging,canDrag等

第二个返回值 代表拖拽元素的ref

第三个返回值 代表拖拽元素拖拽后的预览dom,就是元素被拖拽之后实际的dom

useDrag传入两个参数

第一个参数,描述了drag的配置信息,常用属性

type: 指定元素的类型,只有类型相同的元素才能进行drop操作

item: 元素在拖拽过程中,描述该对象的数据,如果指定的是一个方法,则方法会在开始拖拽时调用,并且需要返回一个对象来描述该元素。

end(item, monitor): 拖拽结束的回调函数,item表示拖拽物的描述数据,monitor表示一个 DragTargetMonitor 实例

**isDragging(monitor)**:判断元素是否在拖拽过程中,可以覆盖Monitor对象中的isDragging方法,monitor表示一个 DragTargetMonitor 实例

代码语言:javascript
复制
isDragging: (monitor) => {
  return monitor.getItem() ? index === monitor.getItem().index : false;
},

collect: (monitor: any) => ({
    //当传入isDragging方法时,monitor.isDragging()方法指代传入的方法
  isDragging: monitor.isDragging(),
}),

**canDrag(monitor)**:判断是否可以拖拽的方法,需要返回一个bool值,可以覆盖Monitor对象中的canDrag方法,与isDragging同理,monitor表示一个 DragTargetMonitor 实例

**collect**:它应该返回一个描述状态的普通对象,然后返回以注入到组件中。它接收两个参数,一个 DragTargetMonitor 实例和拖拽元素描述信息item

第二个参数是一个数组,表示对方法更新的约束,只有当数组中的参数发生改变,才会重新生成方法,基于react的useMemo实现

DragSourceMonitor对象

DragSourceMonitor是传递给拖动源的收集函数的对象。它的方法允许您获取有关特定拖动源的拖动状态的信息。 常用的方法: **canDrag()**:描述元素是否可以拖拽,返回一个bool值

**isDragging()**:判断元素是否在拖拽过程中,返回一个bool值

**getItemType()**:获取元素的类型,返回一个bool值

**getItem()**:获取元素的描述数据,返回一个对象

**getDropResult()**:拖拽结束,返回拖拽结果的构子,可以拿到从drop元素中返回的数据

**didDrop()**: 拖拽结束,元素是否放置成功,返回一个bool值

**getDifferenceFromInitialOffset()**: 获取相对于拖拽起始位置的相对偏移坐标。

useDrop

实现拖拽物放置的钩子,官方用例如下

代码语言:javascript
复制
function BoardSquare({ x, y, children }) {
  const black = (x + y) % 2 === 1
  const [{ isOver }, drop] = useDrop(() => ({
    accept: ItemTypes.KNIGHT,
    drop: () => moveKnight(x, y),
    collect: monitor => ({
      isOver: !!monitor.isOver(),
    }),
  }), [x, y])

  return (
    <div
      ref={drop}
      style={{
        position: 'relative',
        width: '100%',
        height: '100%',
      }}
    >
     ....
    </div>,
  )
}

export default BoardSquare
useDrag返回两个参数
  • 第一个返回值是一个对象,表示关联在拖拽过程中的变量,需要在传入useDrop的规范方法的collect属性中进行映射绑定
  • 第二个返回值代表放置元素的ref
useDrop传入一个参数

用于描述drop的配置信息,常用属性

accept: 指定接收元素的类型,只有类型相同的元素才能进行drop操作

drop(item, monitor): 有拖拽物放置到元素上触发的回调方法,item表示拖拽物的描述数据,monitor表示 DropTargetMonitor实例,该方法返回一个对象,对象的数据可以由拖拽物的monitor.getDropResult方法获得

**hover(item, monitor)**:当拖住物在上方hover时触发,item表示拖拽物的描述数据,monitor表示 DropTargetMonitor实例,返回一个bool值

**canDrop(item, monitor)**:判断拖拽物是否可以放置,item表示拖拽物的描述数据,monitor表示 DropTargetMonitor实例,返回一个bool值

DropTargetMonitor对象

DropTargetMonitor是传递给拖放目标的收集函数的对象。它的方法允许您获取有关特定拖放目标的拖动状态的信息。 常用的方法:

**canDrop()**:判断拖拽物是否可以放置,返回一个bool值

isOver(options): 拖拽物掠过元素触发的回调方法,options表示拖拽物的options信息

**getItemType()**:获取元素的类型,返回一个bool值

**getItem()**:获取元素的描述数据,返回一个对象

**didDrop()**: 拖拽结束,元素是否放置成功,返回一个bool值

**getDifferenceFromInitialOffset()**: 获取相对于拖拽起始位置的相对偏移坐标。

数据流转

看了API之后,实际上不能很好的认识到每个状态和每个方法的工作流程,所以,我这里画了一张图,帮助你更清晰的看到它的数据是如何流动的。

未命名绘图.png
未命名绘图.png

然后我们通过一个demo来更深刻的认识这个过程

任意拖拽.gif
任意拖拽.gif

这里我们定义了几个单词,然后通过拖拽,将它放入对应的drop组里面

单词代码

代码语言:javascript
复制
const Word: FC = ({ type, text, id, ...props }: any) => {
  const [offsetX, setOffsetX] = useState(0);
  const [offsetY, setOffsetY] = useState(0);
  const [{ isDragging }, drag]: any = useDrag(() => ({
    type,
    item: { id, type },
    end(item, monitor) {
      let top = 0,
        left = 0;
      if (monitor.didDrop()) {
        const dropRes = monitor.getDropResult() as any;
        //获取拖拽对象所处容器的数据,获取坐标变化
        if (dropRes) {
          top = dropRes.top;
          left = dropRes.left;
        }
        //这里必须写成函数的传入方式,否则无法获取上一个state
        setOffsetX((offsetX) => offsetX + left);
        setOffsetY((offsetY) => offsetY + top);
      } else {
        // 移出则回到原位
        setOffsetX(0);
        setOffsetY(0);
      }
    },
    collect: (monitor) => ({
      isDragging: !!monitor.isDragging(),
    }),
  }));
  ...
  );
};

分组代码

代码语言:javascript
复制
function Classification({ type, title }: any) {
  const [{ isOver, canDrop }, drop] = useDrop(
    () => ({
      accept: type,
      drop(_item: any, monitor: any) {
        const delta = monitor.getDifferenceFromInitialOffset();
        const left = Math.round(delta.x);
        const top = Math.round(delta.y);
        return { top, left };
      },
      canDrop: (_item, monitor) => {
        const item = monitor.getItem() as any;
        return item.type === type;
      },
      collect: (monitor) => ({
        isOver: !!monitor.isOver(),
        canDrop: !!monitor.canDrop(),
      }),
    }),
    [],
  );
  ....
  )

完整demo戳链接:https://github.com/AdolescentJou/react-dnd-demo ,欢迎star

使用场景

除了上面的例子,还有非常多的案例

批量拖拽

可以选择多个元素进行拖拽

批量拖拽.gif
批量拖拽.gif
拖拽排序

可以拖拽元素放置排序

拖拽卡片排序.gif
拖拽卡片排序.gif

完整demo戳链接:https://github.com/AdolescentJou/react-dnd-demo ,欢迎star

最后

感谢你能看到这里,本文总结了react-dnd的API以及常见的场景,后续会一直更新,希望对你有所帮助,当然,如果可以的话不妨留一个赞再走呢。

参考链接

https://react-dnd.github.io/react-dnd/docs/overview

https://zhuanlan.zhihu.com/p/429986799

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-10-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • theme: channing-cyan
  • 前言
  • 概念
  • 核心API
    • DndProvider
      • Backend
        • useDrag
          • useDrag返回三个参数
          • useDrag传入两个参数
          • DragSourceMonitor对象
        • useDrop
          • useDrag返回两个参数
          • useDrop传入一个参数
          • DropTargetMonitor对象
        • 数据流转
          • 使用场景
            • 批量拖拽
            • 拖拽排序
          • 最后
            • 参考链接
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档