【React】1783- React 拖拽排序组件库对比研究

ccc3294ed004695dbbb41786bf560968.png

作者:青火_

https://juejin.cn/post/7062625911312646175

一、用法简介

基于react的拖拽功能,有这么几个比较流行的库:

  1. react-dnd[1]

  2. react-beautiful-dnd[2]

  3. dnd-kit[3]

  4. react-sortable-hoc[4]

React-dnd

(一)基本概念

  • Backend:后端主要用来抹平浏览器差异,处理 DOM 事件,同时把 DOM 事件转换为 React DnD 内部的 redux action,你可以使用 HTML5 拖拽后端,也可以自定义 touch、mouse 事件模拟的后端实现

  • Item:用一个数据对象来描述当前被拖拽的元素,例如{ cardId: 42 }

  • Type:类似于 redux 里面的actions types 枚举常量,定义了应用程序里支持的拖拽类型

  • Monitor: 拖放本质上是有状态的。要么正在进行拖动操作,要么不在。要么有当前类型和当前项目,要么没有,React DnD 通过 Monitor 来存储这些状态并且提供查询

  • Connector:连接组件和 Backend ,可以让 Backend 获取到 DOM

  • DragSource:这是一个高阶组件,使用它包裹住你的组件使它变为拖拽源

  • DropTarget:这是一个高阶组件,使用它包裹住你的组件使它变为放置源

  • DragDropContext:包裹根组件,提供拖拽的上下文环境

(二)简单demo

59ac7a03c1912101ec57bdf7c95abc17.jpeg

codesandbox.io/s/github/re…[5]

react-beautiful-dnd

(一)基本概念

c4a454f0c22ddd3ef8523e4137f75e60.jpeg 1554693439581c37805f372045ec94aa.jpeg

主要包含三个组件.

  1. DragDropContext : 用于包装拖拽根组件,Draggable和Droppable都需要包裹在DragDropContext内

  2. Draggable 用于包装你需要拖动的组件,使组件能够被拖拽(make it draggable)

  3. Droppable 用于包装接收拖拽元素的组件,使组件能够放置(dropped on it)

(二)简单demo

400310f732da4b0093e94d40c60ef0a2.jpeg 827a2d3b53628720fc0a0c15f11eb5f8.jpeg

codesandbox.io/s/k260nyxq9…[6]

dnd-kit

(一)、基本概念

671ed3976426f1a83d6a89eccfaec3d6.jpeg 2ac63dad341af81c92f8d4b70337d8ef.jpeg

1、DndContext 用于包装拖拽根组件,Draggable和Droppable都需要包裹在DndContext 内
2、Droppable 用于包装接收拖拽元素的组件,使组件能够放置
3、Draggable 用于包装你需要拖动的组件,使组件能够被拖拽
4、Sensors 用于检测不同的输入方法,以启动拖动操作、响应移动以及结束或取消操作,内置传感器有:

  • 指针

  • 鼠标

  • 触摸

  • 键盘

5、Modifiers 可让您动态修改传感器检测到的运动坐标。它们可用于广泛的用例,例如:

  • 将运动限制在单个轴上

  • 限制可拖动节点容器的边界矩形的运动

  • 限制可拖动节点的滚动容器边界矩形的运动

  • 施加阻力或夹紧运动

(二)、简单demo

9f0d755ba6417603a305b848db003761.jpeg

5fc05e08a4a65d0021ae0bf2-ffprtowwny.chromatic.com/iframe.html…[7]

react-sortable-hoc

(一)、基本概念
1、SortableContainer 拖拽排序的容器
2、SortableElement 拖拽排序的元素

(二)、简单demo

22059cd8e795d4ce2c5417e9e20c9750.jpeg

codesandbox.io/s/react-sor…[8]

三、兼容antd的table

如何配合antd的table组件进行使用?

838410df98a27e5bd960504c04c4578a.jpeg

react-dnd 使用antd-table :codesandbox.io/s/tuo-zhuai…[9]

feacffd386a1b952cd386259bb9fa0da.jpeg

react-sortable-hoc使用antd-table:codesandbox.io/s/tuo-zhuai…[10]

9fe773d34c4a4a77099984aa4e27cc38.jpeg

react-beautiful-dnd-antd-table:codesandbox.io/s/react-bea…[11]

dnd-kit:stackblitz.com/edit/react-…[12](该demo无法运行,github.com/clauderic/d…[13] 根据这个issue说是antd-design自身的原因,

a6364f1005fd3f1fd09c3f1cca438e66.jpeg

在底层增强了表格组件)

四、树兼容

antd自带的tree拖拽排序:
codepen.io/huxinmin/em…[14]

自带的tree拖拽缺点是

  1. 无法实现动态实时拖拽更换位置效果,必须拖拽结束后才发生位置变化

  2. 需要修改大量的自带的样式

可以简单地把树看做是互相嵌套的列表。

2d41924511f086e2fc8d975fee2a97ff.jpeg

react-dnd:codesandbox.io/s/crazy-hoo…[15]

5bbfadf79d553703367fb156620d80e1.jpeg

react-sortable-hoc:codesandbox.io/embed/react…[16]

8d302173d9bf08881ad37d22861783fd.jpeg

react-beautiful-dnd-antd-table:codesandbox.io/embed/react…[17]

2f67f0075d8d3231449cbf83b8e026be.jpeg

dnd-kit:codesandbox.io/embed/react…[18]

五、移动端兼容

28929a0b3182aab5a62d872accc6f498.jpeg

react-dnd:codesandbox.io/embed/react…[19]

83eb5504639a01469d9154398db80c2f.jpeg

react-beautiful-dnd-antd-table:codesandbox.io/s/react-bea…[20]

3f2b6fa9ba753d8eec9c06a1ede34642.jpeg

react-sortable-hoc使用antd-table:codesandbox.io/s/tuo-zhuai…[21]

1b3e3d07e8c1bf9c7181a9ef36a684a1.jpeg

dnd-kit:5fc05e08a4a65d0021ae0bf2-hbqxtqukzi.chromatic.com/iframe.html…[22]

六、无限滚动

最佳的方法就是使用virtual-list,不过这几个库的支持情况也不一样。

0758dc6e7a18c06d4d0d2ec45122d994.jpeg

react-dnd:codesandbox.io/embed/react…[23]

使用requestAnimationFrame进行性能优化,也可以配合其他的虚拟list库进行使用。

20d461ecb5803300257a4ea3458cd3e3.jpeg

dnd-kit:5fc05e08a4a65d0021ae0bf2-hbqxtqukzi.chromatic.com/iframe.html…[24]

这个demo使用了react-tiny-virtual-list[25]库。

f54b17f7114066935c8a573840aad428.jpeg

react-sortable-hoc:clauderic.github.io/react-sorta…[26]

使用了react-virtualized

7b8d0c8c3d63812581c58f317f17e03c.jpeg

react-beautifule-dnd:react-beautiful-dnd.netlify.app/iframe.html…[27]

使用了react-virtualized。

七、总结对比

  • react-dnd[28]

  • 文档齐全

  • github star星数16.4k

  • 维护更新良好,最近一月内有更新维护

  • 学习成本较高

  • 功能中等

  • 移动端兼容情况,良好

  • 示例数量中等

  • 概念较多,使用复杂

  • 组件间能解耦

  • react-beautiful-dnd[29]

  • 文档齐全

  • github star星数24.8k

  • 维护更新良好,最近三月内有更新维护

  • 学习成本较高

  • 使用易度中等

  • 功能丰富

  • 移动端兼容情况,优秀

  • 示例数量丰富

  • 是为垂直和水平列表专门构建的更高级别的抽象,没有提供 react-dnd 提供的广泛功能

  • 外观漂亮,可访问性好,物理感知让人感觉更真实的在移动物体

  • 开发理念上是拖拽,不支持copy/clone

  • dnd-kit[30]

  • 文档齐全

  • github star星数2.8k

  • 维护更新良好,最近一月内有更新维护

  • 学习成本中等

  • 使用易度中等

  • 功能中等

  • 移动端兼容情况,中等

  • 示例数量丰富

  • 未看到copy/clone

  • react-sortable-hoc[31]

  • 文档较少

  • github star星数9.5k

  • 维护更新良好,最近三月内有更新维护

  • 学习成本较低

  • 使用易度较低

  • 功能简单

  • 移动端兼容情况,中等

  • 示例数量中等

  • 不支持拖拽到另一个容器中

  • 未看到copy/clone

  • 主要集中于排序功能,其余拖拽功能不丰富

如果是要结合antd的table使用,最简单的组件是react-sortable-hoc,如果是无限滚动react-sortable-hoc示例虽然多,但是源码很少,可以考虑使用react-beautiful-dnd。如果是树形拖拽,要求不高的情况可以使用antd自带的tree,要求高点可以使用react-beautiful-dnd。兼容移动端,可以考虑使用react-sortable-hoc或者react-beautiful-dnd。

八、如何自己封装一个简单的拖拽组件

一、HTML5拖放API

首先,为了使元素可以拖动,需要设置draggable属性:

<img draggable="true">

然后有这么几个拖拽处理的函数:

  1. ondrag 拖放进行中

  2. ondragend/ondragstart 开始拖放和结束拖放

  3. ondragover 当元素或选中的文本被拖到一个目标目标上(每100毫秒触发一次)。

  4. ondragenter/ondragleave 源对象开始进入/离开目标对象范围内

  5. ondrop 源对象被拖放到目标对象上

数据的传输,使用event.dataTransfer,它有如下这些api:

  • setData: 添加拖拽数据,这个方法接收两个参数,第一个参数是数据类型(可自定义),第二个参数是对应的数据

  • getData:反向操作,获取数据,只接收一个参数,即数据类型

  • clearData: 清除数据

  • setDragImage: 可自定义拖放过程中鼠标旁边的图像

  • effectAllowed: 属性指定拖放操作所允许的一个效果。_copy_ 操作用于指示被拖动的数据将从当前位置复制到放置位置。_move操作用于指定被拖动的数据将被移动。link_操作用于指示将在源和放置位置之间创建某种形式的关系或连接。

二、功能与架构设计

  1. 使用react-hooks

  2. 拖拽对象drag组件,拖放对象drop组件,拖拽上下文dndContext

  3. 支持移动端

  4. 支持排序

三、代码

Drag组件使用:

<Drag index={1} id='1'>
  <div>被包裹的可以拖拽的组件</div>
</Drag>

Drag组件实现:

import { FC } from "react";

interface DragProps {
  index: number;
  id: string | number;
}

const Drag: FC<DragProps> = (props) => {
  const startDrag = (ev) => {
    // 传输数据
    ev.dataTransfer.setData("index", props.index);
    ev.dataTransfer.setData("id", props.id);
  };

  return (
    <div draggable onDragStart={startDrag}>
      {props.children}
    </div>
  );
};

export default Drag;

Drop组件使用:

<Drop>
 <Drag index={1} id='1'>
   <div>被包裹的可以拖拽的组件</div>
 </Drag>
</Drop>

Drop组件实现:

import { FC, useContext } from "react";
import { Context } from "./DndContext";

const Drop: FC = (props) => {
  const { onDragOver, onDragEnd } = useContext(Context);
  const dragOver = (ev) => {
    ev.preventDefault();
    if (onDragOver) onDragOver();
  };

  const drop = (ev) => {
    // 获取数据
    const oldIndex = ev.dataTransfer.getData("index");
    // 获取拖拽结束时的Y轴坐标
    const Y = ev.clientY;
    // 简便计算,设定高度为20
    // 我这里很偷懒,实际计算情况很复杂
		//一般有两种实现思路,一种就是根据位置计算,另外一种就是给拖拽源设置可放置,然后获取
    const height = 20;
    const newIndex = Math.floor(Y / height);

    if (oldIndex) {
      if (onDragEnd) onDragEnd(Number(oldIndex), newIndex);
    }
  };

  return (
    <div onDragOver={dragOver} onDrop={drop}>
      {props.children}
    </div>
  );
};

export default Drop;

DndContext组件使用:

<DndContext
        onDragEnd={(oldIndex, newIndex) => {
          setData(arrayMove(data, oldIndex, newIndex));
        }}
        onDragOver={() => {}}
      >
        <Drop>
          {data.map((i, index) => (
            <Drag key={i.id} id={i.id} index={index}>
              <div className="item">{i.text}</div>
            </Drag>
          ))}
        </Drop>
      </DndContext>

DndContext组件实现:

import { createContext, FC } from "react";

export interface TContext {
  onDragOver: () => void;
  onDragEnd: (oldIndex: number, newIndex: number) => void;
}

const Context = createContext<TContext>({} as TContext);

const DndContext: FC<TContext> = (props) => {
  return (
    <Context.Provider
      value={{
        onDragEnd: (oldIndex, newIndex) => {
          props.onDragEnd(oldIndex, newIndex);
        },
        onDragOver: () => {
          props.onDragOver();
        }
      }}
    >
      {props.children}
    </Context.Provider>
  );
};

export { Context };
export default DndContext;

如果需要处理移动端的兼容性,可以使用如下库:
github.com/timruffles/…[32]

四、优化空间

  1. 拖拽结束,所在位置计算

  2. 拖拽过程中实时交换位置

  3. 性能优化

  4. 异常处理等

  5. 拖拽过程样式

  6. 拖拽方向,x轴和Y轴

  7. 等等

五、在线代码

具体在线代码示例:codesandbox.io/embed/react…[33]

8bc6b26a973ac56a080d7ec8e411f793.jpeg

参考资料

向下滑动查看

[1]

github.com/react-dnd/react-dnd: https://link.juejin.cn?target=github.com%2Freact-dnd%2Freact-dnd

[2]

github.com/atlassian/react-beautiful-dnd: https://link.juejin.cn?target=github.com%2Fatlassian%2Freact-beautiful-dnd

[3]

github.com/clauderic/dnd-kit: https://link.juejin.cn?target=github.com%2Fclauderic%2Fdnd-kit

[4]

github.com/clauderic/react-sortable-hoc: https://link.juejin.cn?target=github.com%2Fclauderic%2Freact-sortable-hoc

[5]

codesandbox.io/s/github/react-dnd/react-dnd/tree/gh-pages/examples_hooks_ts/04-sortable/simple%3Ffrom-embed%3D%26file%3D/src/Container.tsx: https://link.juejin.cn?target=codesandbox.io%2Fs%2Fgithub%2Freact-dnd%2Freact-dnd%2Ftree%2Fgh-pages%2Fexamples_hooks_ts%2F04-sortable%2Fsimple%253Ffrom-embed%253D%2526file%253D%2Fsrc%2FContainer.tsx

[6]

codesandbox.io/s/k260nyxq9v: https://link.juejin.cn?target=codesandbox.io%2Fs%2Fk260nyxq9v

[7]

5fc05e08a4a65d0021ae0bf2-ffprtowwny.chromatic.com/iframe.html%3Fid%3Dpresets-sortable-vertical--basic-setup%26viewMode%3Dstory: https://link.juejin.cn?target=5fc05e08a4a65d0021ae0bf2-ffprtowwny.chromatic.com%2Fiframe.html%253Fid%253Dpresets-sortable-vertical--basic-setup%2526viewMode%253Dstory

[8]

codesandbox.io/s/react-sortable-hoc-starter-o104x95y86: https://link.juejin.cn?target=codesandbox.io%2Fs%2Freact-sortable-hoc-starter-o104x95y86

[9]

codesandbox.io/s/tuo-zhuai-pai-xu-antd-4-17-0-alpha-0-forked-tnu3u%3Ffile%3D/row.js: https://link.juejin.cn?target=codesandbox.io%2Fs%2Ftuo-zhuai-pai-xu-antd-4-17-0-alpha-0-forked-tnu3u%253Ffile%253D%2Frow.js

[10]

codesandbox.io/s/tuo-zhuai-shou-bing-lie-antd-4-17-0-alpha-0-forked-c5l4x%3Ffile%3D/index.js: https://link.juejin.cn?target=codesandbox.io%2Fs%2Ftuo-zhuai-shou-bing-lie-antd-4-17-0-alpha-0-forked-c5l4x%253Ffile%253D%2Findex.js

[11]

codesandbox.io/s/react-beautiful-dnd-examples-multi-drag-table-with-antd-forked-rln36: https://link.juejin.cn?target=codesandbox.io%2Fs%2Freact-beautiful-dnd-examples-multi-drag-table-with-antd-forked-rln36

[12]

stackblitz.com/edit/react-waoqzs%3Ffile%3Dsrc%252FApp.js: https://link.juejin.cn?target=stackblitz.com%2Fedit%2Freact-waoqzs%253Ffile%253Dsrc%25252FApp.js

[13]

github.com/clauderic/dnd-kit/issues/310: https://link.juejin.cn?target=github.com%2Fclauderic%2Fdnd-kit%2Fissues%2F310

[14]

codepen.io/huxinmin/embed/bGRLmjP: https://link.juejin.cn?target=codepen.io%2Fhuxinmin%2Fembed%2FbGRLmjP

[15]

codesandbox.io/s/crazy-hoover-vwcy9%3Ffile%3D/src/Container.tsx: https://link.juejin.cn?target=codesandbox.io%2Fs%2Fcrazy-hoover-vwcy9%253Ffile%253D%2Fsrc%2FContainer.tsx

[16]

codesandbox.io/embed/react-sortable-hoc-tree-386oh%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark: https://link.juejin.cn?target=codesandbox.io%2Fembed%2Freact-sortable-hoc-tree-386oh%253Ffontsize%253D14%2526hidenavigation%253D1%2526theme%253Ddark

[17]

codesandbox.io/embed/react-beautiful-dnd-tree-63f3j%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark: https://link.juejin.cn?target=codesandbox.io%2Fembed%2Freact-beautiful-dnd-tree-63f3j%253Ffontsize%253D14%2526hidenavigation%253D1%2526theme%253Ddark

[18]

codesandbox.io/embed/react-dnd-kit-tree-uiujv%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark: https://link.juejin.cn?target=codesandbox.io%2Fembed%2Freact-dnd-kit-tree-uiujv%253Ffontsize%253D14%2526hidenavigation%253D1%2526theme%253Ddark

[19]

codesandbox.io/embed/react-dnd-antd-table-mobile-j1b0l%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark: https://link.juejin.cn?target=codesandbox.io%2Fembed%2Freact-dnd-antd-table-mobile-j1b0l%253Ffontsize%253D14%2526hidenavigation%253D1%2526theme%253Ddark

[20]

codesandbox.io/s/react-beautiful-dnd-examples-multi-drag-table-with-antd-forked-rln36: https://link.juejin.cn?target=codesandbox.io%2Fs%2Freact-beautiful-dnd-examples-multi-drag-table-with-antd-forked-rln36

[21]

codesandbox.io/s/tuo-zhuai-shou-bing-lie-antd-4-17-0-alpha-0-forked-c5l4x%3Ffile%3D/index.js: https://link.juejin.cn?target=codesandbox.io%2Fs%2Ftuo-zhuai-shou-bing-lie-antd-4-17-0-alpha-0-forked-c5l4x%253Ffile%253D%2Findex.js

[22]

5fc05e08a4a65d0021ae0bf2-hbqxtqukzi.chromatic.com/iframe.html%3Fid%3Dpresets-sortable-vertical--basic-setup%26viewMode%3Dstory: https://link.juejin.cn?target=5fc05e08a4a65d0021ae0bf2-hbqxtqukzi.chromatic.com%2Fiframe.html%253Fid%253Dpresets-sortable-vertical--basic-setup%2526viewMode%253Dstory

[23]

codesandbox.io/embed/react-dnd-stress-test-pbbjk%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark: https://link.juejin.cn?target=codesandbox.io%2Fembed%2Freact-dnd-stress-test-pbbjk%253Ffontsize%253D14%2526hidenavigation%253D1%2526theme%253Ddark

[24]

5fc05e08a4a65d0021ae0bf2-hbqxtqukzi.chromatic.com/iframe.html%3Fid%3Dpresets-sortable-virtualized--basic-setup%26args%3D%26viewMode%3Dstory: https://link.juejin.cn?target=5fc05e08a4a65d0021ae0bf2-hbqxtqukzi.chromatic.com%2Fiframe.html%253Fid%253Dpresets-sortable-virtualized--basic-setup%2526args%253D%2526viewMode%253Dstory

[25]

github.com/clauderic/react-tiny-virtual-list: https://link.juejin.cn?target=github.com%2Fclauderic%2Freact-tiny-virtual-list

[26]

clauderic.github.io/react-sortable-hoc/%23/react-virtualized/basic-usage%3F_k%3Djgwno1: https://link.juejin.cn?target=clauderic.github.io%2Freact-sortable-hoc%2F%2523%2Freact-virtualized%2Fbasic-usage%253F_k%253Djgwno1

[27]

react-beautiful-dnd.netlify.app/iframe.html%3Fid%3Dvirtual-react-virtualized--list: https://link.juejin.cn?target=react-beautiful-dnd.netlify.app%2Fiframe.html%253Fid%253Dvirtual-react-virtualized--list

[28]

github.com/react-dnd/react-dnd: https://link.juejin.cn?target=github.com%2Freact-dnd%2Freact-dnd

[29]

github.com/atlassian/react-beautiful-dnd: https://link.juejin.cn?target=github.com%2Fatlassian%2Freact-beautiful-dnd

[30]

github.com/clauderic/dnd-kit: https://link.juejin.cn?target=github.com%2Fclauderic%2Fdnd-kit

[31]

github.com/clauderic/react-sortable-hoc: https://link.juejin.cn?target=github.com%2Fclauderic%2Freact-sortable-hoc

[32]

github.com/timruffles/mobile-drag-drop: https://link.juejin.cn?target=github.com%2Ftimruffles%2Fmobile-drag-drop

[33]

codesandbox.io/embed/react-drag-w03jd%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark: https://link.juejin.cn?target=codesandbox.io%2Fembed%2Freact-drag-w03jd%253Ffontsize%253D14%2526hidenavigation%253D1%2526theme%253Ddark

往期回顾

#

如何使用 TypeScript 开发 React 函数式组件?

#

11 个需要避免的 React 错误用法

#

6 个 Vue3 开发必备的 VSCode 插件

#

3 款非常实用的 Node.js 版本管理工具

#

6 个你必须明白 Vue3 的 ref 和 reactive 问题

#

6 个意想不到的 JavaScript 问题

#

试着换个角度理解低代码平台设计的本质

67e6e811f175c455840eb9d427409fe1.gif

回复“加群”,一起学习进步

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值