昆仑山上一棵草 發表於 2021-1-12 15:56:00

react-dnd 拖拽排序

<h3 id="开始">开始</h3>
<p>最近刚刚做完自定义表单的组件,分享一下拖拽排序。</p>
<h3 id="效果图">效果图</h3>
<p><img src="https://img2020.cnblogs.com/blog/1235934/202101/1235934-20210112155258999-944538356.gif" alt="" loading="lazy"></p>
<h3 id="react-dnd使用说明">react-dnd使用说明</h3>
<p>必须是这样格式的,不然会报 找不到上下文</p>
<pre><code>&lt;DndProvider backend={HTML5Backend}&gt;
   &lt;useDrop&gt;
      &lt;useDrag&gt;&lt;/useDrag&gt;
   &lt;/useDrop&gt;
&lt;/DndProvider&gt;
</code></pre>
<h3 id="准备工作">准备工作</h3>
<blockquote>
<p>官方文档</p>
</blockquote>
<p>https://react-dnd.github.io/react-dnd/about</p>
<blockquote>
<p>安装react-dnd、react-dnd-html5-backend</p>
</blockquote>
<pre><code class="language-javascript">npm install react-dnd react-dnd-html5-backend
</code></pre>
<h3 id="开始开发">开始开发</h3>
<blockquote>
<p>项目是基于ts的,不需要可自行删除</p>
</blockquote>
<h4 id="定义父组件">定义父组件</h4>
<pre><code>const Drag:React.FC&lt;{}&gt; = props =&gt; {
return (
    &lt;div className="drag-wrapper"&gt;
      {/* DndProvider组件提供了react-dnd的功能,必须通过backend绑定HTML5Backend*/}
      &lt;DndProvider backend={HTML5Backend}&gt;
      &lt;DragSortComponent/&gt;
      &lt;/DndProvider&gt;
    &lt;/div&gt;
)
}

export default Drag
</code></pre>
<h4 id="定义内容组件">定义内容组件</h4>
<pre><code>const DragSortComponent:React.FC&lt;{}&gt; = props =&gt; {
const = useState&lt;{id:number,name:string}[]&gt;(List)
const = useState&lt;{ key: number | null; value: string }&gt;({
    key: null,
    value: '',
})
// 拖拽后的值
const sortItems = useRef&lt;{ dragRow: any; placeRow: any; posi: string }&gt;({
    dragRow: {},
    placeRow: {},
    posi: '',
})
// 拖拽结束后的方法
const onDrop = (item: any, monitor: DropTargetMonitor) =&gt; {
    const { dragRow, placeRow, posi } = sortItems.current
    let _map: any[] = JSON.parse(JSON.stringify(list))
    let index1 = _map.findIndex(v =&gt; v.id === dragRow.id) // 拖拽的itemIndex
    _map.splice(index1, 1) // 先删掉拖拽的,在获取放置的
    let index = _map.findIndex(v =&gt; v.id === placeRow.id) // 放置的itemIndex
    if (index !== -1 &amp;&amp; index1 !== -1) {
      _map.splice(posi === 'bottom' ? index + 1 : index, 0, dragRow)
      setList(() =&gt; _map)
    }
}
// DragSortItemComponent组件是通用排序组件,所以需要在父组件在定义一个useDrop,来改变数据 必须挂载在父级div
const [, drop] = useDrop({
    accept: 'sort', // 必须和拖拽的accept一致
    drop: onDrop,
    collect: monitor =&gt; ({
      isOver: monitor.isOver({ shallow: true }),
      canDrop: monitor.canDrop(),
    }),
})
const onItemDragClass = (key:number,value:string) =&gt; {
    if (itemClass.value !== value) {
      setItemClass(() =&gt; {
      let data = { key, value }
      return data
      })
    }
}
const onSortItemChange = (dragRow: any, placeRow: any, posi: string) =&gt; {
    sortItems.current = { dragRow, placeRow, posi }
}
return (
    &lt;div className="drag-sort-component-wrapper" ref={drop}&gt;
      {
      list.map(v =&gt; (
          &lt;DragSortItemComponent
            key={v.id}
            row={v}
            onItemDragClass={onItemDragClass}
            onSortItemChange={onSortItemChange}
            keyName="id"
          &gt;
            &lt;div className={['drag-item ',itemClass.key === v.id ? itemClass.value : ''].join(' ')}&gt;{v.name}&lt;/div&gt;
          &lt;/DragSortItemComponent&gt;
      ))
      }
    &lt;/div&gt;
)
}
</code></pre>
<h4 id="定义拖拽排序组件">定义拖拽排序组件</h4>
<pre><code>/**
* 通用拖拽排序的容器
* @param row 当前行
* @param onItemDragClass 拖拽过程的样式 top | bottom
* @param onSortItemChange 拖拽结束后返回的值 dragRow 当前拖拽 placeRow 放置的,posi 位置 top | bottom
* @param keyName 键名
*/
type IProps = {
row: any,
onItemDragClass: (key:number,value:string) =&gt; void,
onSortItemChange: (dragRow: any, placeRow: any, posi: string) =&gt; void // 排序后
keyName: string
}
const DragSortItemComponent:React.FC&lt;IProps&gt; = props =&gt; {
const { row,onItemDragClass,onSortItemChange,keyName } = props
const ref = useRef&lt;HTMLDivElement&gt;(null)
/**
   * 拖拽容器
   */
const [, drop] = useDrop({
    // 定义拖拽的类型
    accept: 'sort',
    drop: (item, monitor) =&gt; {
      const didDrop = monitor.didDrop()
      if (didDrop) {
      return
      }
    },
    canDrop: (item, mointor) =&gt; {
      // 阻止默认拖拽释放
      onItemDragClass(row, '')
      return false
    },
    hover: (item: any, monitor) =&gt; {
      const didHover = monitor.isOver({ shallow: true })
      if (didHover) {
      // 拖拽目标的id
      const dragIndex = item
      // 放置目标id可以用index | id 只要是number,数据里唯一的就可以
      const hoverIndex = row
      // 如果一样不处理
      if (dragIndex === hoverIndex) {
          onItemDragClass(row, '')
          return
      }
      // 获取放置的位置
      const hoverBoundingRect = ref.current?.getBoundingClientRect() as DOMRect
      // 获取放置的Y轴中点
      const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
      // 获取拖拽目标偏移量
      const clientOffset = monitor.getClientOffset() as XYCoord
      const hoverClientY = clientOffset.y - hoverBoundingRect.top
      if (dragIndex !== hoverIndex) {
          if (hoverMiddleY &lt; hoverClientY) {
            onItemDragClass(row, 'bottom')
          } else {
            onItemDragClass(row, 'top')
          }
          // 如果不做成通用拖拽容器,把参数存起来,把这个放在useDrag的end方法里,
          onSortItemChange(item, row, hoverMiddleY &lt; hoverClientY ? 'bottom' : 'top')
      }
      }
    },
    collect: monitor =&gt; ({
      isOver: monitor.isOver({ shallow: true }),
      canDrop: monitor.canDrop(),
    }),
})
/**
   * 定义拖拽
   * isDragging 是否拖拽
   */
const [{ isDragging }, drag] = useDrag({
    item: { ...row, type: 'sort'},
    end: () =&gt; {
      // onSortItemChange(item, row, hoverMiddleY &lt; hoverClientY ? 'bottom' : 'top')
    },
    collect: monitor =&gt; ({
      isDragging: monitor.isDragging(),
      didDrop: monitor.isDragging(),
    }),
})
drop(drag(ref))
return (
    &lt;div ref={ref} style={{ opacity: isDragging ? 0 : 1 }}&gt;
      {props.children}
    &lt;/div&gt;
)
}
</code></pre>
<h3 id="github地址">github地址</h3>
<p>https://github.com/GeFei-someone/drag-sort</p><br><br>
来源:https://www.cnblogs.com/feiyu159/p/14267401.html
頁: [1]
查看完整版本: react-dnd 拖拽排序