react中实现一个简单的拖拽排序react-dnd

4,304 阅读3分钟

React DnD 是什么?

React DnD是React和Redux核心作者 Dan Abramov创造的一组React 高阶组件,可以在保持组件分离的前提下帮助构建复杂的拖放接口;

其中一些概念类似于Flux和Redux架构。

这并非巧合,因为 React DnD 在内部使用 Redux。

开始之前我们先来看一下效果;

先上代码:

import React, {    useState, useCallback, useRef, useEffect,  } from 'react';  import { DndProvider, useDrag, useDrop } from 'react-dnd';  import { HTML5Backend } from 'react-dnd-html5-backend';  import update from 'immutability-helper';  import { CloseOutlined, CloseCircleFilled } from '@ant-design/icons';  import PropTypes from 'prop-types';  import './dragModule.scss';    const type = 'DragableBodyRow';    const DragableBodyRow = ({    index, moveRow, className, style, title, value, onRemoveClick,  }) => {    const ref = useRef();    const [{ isOver, dropClassName }, drop] = useDrop(      () => ({        accept: type,        collect: (monitor) => {          const { index: dragIndex } = monitor.getItem() || {};          if (dragIndex === index) {            return {};          }          return {            isOver: monitor.isOver(),            dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',          };        },        drop: (item) => {          moveRow(item.index, index);        },      }),      [index, moveRow],    );    const [, drag] = useDrag(      () => ({        type,        item: { index },        // item: (monitor) => {        //   console.log(monitor);        // }        // canDrag: index > 1,        // 收集器函数        // collect: monitor => ({        //   isDragging: monitor.isDragging(),        // }),      }),      [index],    );    drag(drop(ref));    return (      <div        ref={ref}        className={`${className}${isOver ? dropClassName : ''}`}        style={{ cursor: 'move', ...style }}      >        <span className="drag-list-item">          <span className="drag-list-item-content">{title}</span>          <CloseOutlined className="drag-list-item-remove" onClick={() => onRemoveClick(value)} />        </span>      </div>    );  };  DragableBodyRow.propTypes = {    index: PropTypes.number,    moveRow: PropTypes.func.isRequired,    className: PropTypes.string,    style: PropTypes.object,    title: PropTypes.string,    value: PropTypes.number,    onRemoveClick: PropTypes.func,  };    const DragModule = ({ data, onSelectChange, selectedKeys }) => {    const [dragData, setData] = useState([]);    useEffect(() => {      let currentData = [];      if (dragData.length > data.length) {        currentData = dragData.filter(a => data.find(b => a.key === b.key));      } else {        currentData = dragData.concat(data.filter(a => !(dragData.find(b => a.key === b.key))));      }      setData(currentData);    // eslint-disable-next-line react-hooks/exhaustive-deps    }, [data]);      const moveRow = useCallback(      (dragIndex, hoverIndex) => {        const dragRow = dragData[dragIndex];        setData(          update(dragData, {            $splice: [              [dragIndex, 1],              [hoverIndex, 0, dragRow],            ],          }),        );      },      [dragData],    );      const onRemoveClick = (key) => {      if (key) {        const newSelectedKeys = selectedKeys.filter(i => i !== key);        onSelectChange(newSelectedKeys);      } else {        onSelectChange([]);      }    };    console.log(dragData);      return (      <DndProvider backend={HTML5Backend}>        <div className="tag-select-drag">          <div className="tag-select-drag-overflow">            {              dragData.map((item, index) => (                <DragableBodyRow key={String(index)} index={index} moveRow={moveRow} title={item.title} value={item.key} onRemoveClick={onRemoveClick} className="drag-list-overflow" />              ))            }          </div>          <CloseCircleFilled className="tag-select-drag-allclear" onClick={() => onRemoveClick()} />        </div>      </DndProvider>    );  };    DragModule.propTypes = {    value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),    onSelectChange: PropTypes.func,  };  DragModule.defaultProps = {    value: undefined,    onSelectChange: () => {},  };    export default DragModule;  
  

完整代码地址:github.com/NanHai-Z/re…

安装

npm install react-dnd react-dnd-html5-backend

React DnD 的基本概念

items & types

与 Flux(或 Redux)一样,React DnD 使用数据而不是视图作为事实的来源。当您在屏幕上拖动某物时,我们并不是说正在拖动组件或 DOM 节点。

所以这里用一个“数据对象对象items”描述拖动的元素,例如{id: 1}

types类型是唯一标识应用程序中整个项目类别的字符串(或符号),类型很有用,因为随着您的应用程序的增长您可能希望使更多内容可拖动,但您不一定希望所有现有的放置目标突然开始对新项目做出反应。这些类型允许您指定兼容的拖放源和放置目标。您可能会在您的应用程序中枚举类型常量,类似于您可能如何枚举 Redux 操作类型。

Backend

React DnD 使用HTML5 拖放 API。

这是一个合理的默认设置,因为它会截取拖动的 DOM 节点并将其用作开箱即用的“拖动预览”。当光标移动时,您不必进行任何绘图,这很方便。此 API 也是处理文件放置事件的唯一方法。

它所做的就是将 DOM 事件转换为 React DnD 可以处理的内部 Redux 操作

Monitors

React DnD 通过 Monitor 来存储拖放状态并且提供查询;

Connectors

Backend 关注 DOM 事件,组件关注拖放状态,connector 可以连接组件和 Backend ,可以让 Backend 获取到 DOM。

Hooks API

useDrag

该useDrag钩子提供了一种将您的组件作为拖动源连接到 DnD 系统的方法。

import { useDrag } from 'react-dnd'

function DraggableComponent(props) {
  const [collected, drag, dragPreview] = useDrag(() => ({
    type, // 必须,唯一标识
    item: { id }, // 必须:描述被拖动数据的普通 JavaScript 对象
    collect: () => {},// 可选, 返回的对象供组件使用 -> collectedProps
    canDrag: () => {}, // 元素是否可拖拽
    ...
  }))
  return collected.isDragging ? (
    <div ref={dragPreview} />
  ) : (
    <div ref={drag} {...collected}>
      ...
    </div>
  )
}

collected: 包含从 collect 函数收集的属性的对象。如果没有collect定义函数,则返回一个空对象。

drag: 拖动源的连接器功能。这必须附加到 DOM 的可拖动部分。

dragPreview: 拖动预览的连接器功能。这可能会附加到 DOM 的预览部分。

useDrop

该useDrop钩子提供了一种将组件连接到 DnD 系统作为放置目标的方法。

import { useDrop } from 'react-dnd'

function myDropTarget(props) {
  const [collectedProps, drop] = useDrop(() => ({
    accept, // 必须,与useDrag中的type对应;
    collect: () => {},// 可选, 返回的对象供组件使用 -> collectedProps
    drop: () => {}, // 可选的。在目标上放置兼容项目时调用。
    hover: () => {}, // 可选的。当项目悬停在组件上时调用。
    canDrop: () => {}, // 可选, 元素是否可放置
  }))

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

collected: 包含从 collect 函数收集的属性的对象。如果没有collect定义函数,则返回一个空对象。

drop:放置目标的连接器功能。这必须附加到 DOM 的放置目标部分。

更好的更新复杂数据

immutability-helper 

reactjs.org/docs/update…

参考:

developer.mozilla.org/zh-CN/docs/…

github.com/react-dnd/r…

react-dnd.github.io/react-dnd/d…