React +TS实现拖拽列表

使用React+TS编写逻辑代码,less编写样式代码,不依赖第三方库,开箱即用,

最近写的拖拽组件,分享给大家,直接上代码

首先看看如何使用

自己定义的组件需要包裹在DragList.Item组件中

import DragList from './DragList';


  {
        console.log(info)
      }}
    >
      111
      222
      333
      444
      555
    

组件展示

拖拽组件.gif

TS 部分

import React, { useState, useEffect, useRef, useMemo, useLayoutEffect } from "react";
import styles from "./index.module.less"
export interface listDataChangeType {
    originIndex: number,// 移动前的索引 或 点击删除的索引 或者新增的组件的索引
    targetIndex?: number,// 移动后的目标索引
    type: 'move' // 操作的类型
}
interface DragListProps {
    listDataChange: (changeInfo: listDataChangeType) => void //数据变化
    children: React.ReactNode
}
enum MoveDownAndUp {
    down = 1,
    up = -1
}
const DragItem: React.FC<{children:React.ReactNode}> = ({ children }) => {
    useLayoutEffect(() => {
        let dragItem = Array.from(document.querySelectorAll('div[data-drag-warper]'))
        // 最大值
        let maxHeight = Math.max.apply(Math, dragItem.map(item => { return item['clientHeight'] }))
        dragItem.forEach((item: any) => {
            item.style.setProperty('--drag-offest-height', `${maxHeight}px`)
        })

    }, [])
    return 
{children}
} const DragList: React.FC = ({ children, listDataChange}) => { /** 是否显示遮罩层,实现移动和鼠标松开 */ const [dragging, setDragging] = useState(false); /** 当前索引数 */ const [draggingItemIndex, setDraggingItemIndex] = useState(-1); const [originIndex, setOriginIndex] = useState(-1) /** 当前点击移动列的y轴偏移量 */ const [startPageY, setStartPageY] = useState(0); /** 设置移动列偏移量 */ const [offsetY, setOffsetY] = useState(0); // 偏移的高度 const [lineHeight, setLineHeight] = useState(0) const listContainer = useRef(null) const [childList, setChildList] = useState([]) const [popoverOpen, setPopoverOpen] = useState(false) // 处理childran const validChildren: any = useMemo(() => { return React.Children.map(children, (child) => { if (!React.isValidElement(child)) return null if (child.type !== DragItem) { console.error('The children of `DragList` must be `DragList.Item` components.') return null } return React.cloneElement(child, { ...child.props }) }) }, [children]) // 计算偏移位置 useEffect(() => { let clientHeight = listContainer?.current?.clientHeight || 0 let length = childList.length if (clientHeight > 0) { setLineHeight((clientHeight - 10 * (length - 1)) / length) } }, [childList]) // 避免修改validChildren useEffect(() => { setChildList(validChildren) }, [validChildren]) /** 拖拽开始 */ const onDragStart = (event: React.DragEvent, index: number) => { if (childList.length === 1) return setDragging(true); setDraggingItemIndex(index); setStartPageY(event.pageY); setOriginIndex(index) }; /** 鼠标松开 */ const handleMouseUp = () => { if (childList.length === 1) return if (originIndex !== draggingItemIndex) { // 不相等说明移动了 listDataChange({ originIndex, targetIndex: draggingItemIndex, type: 'move' }) } // 还原配置 setDragging(false); setDraggingItemIndex(-1); setOriginIndex(-1) setStartPageY(0); }; /** * 当拖放生效时,重新整理数组 * @param startIndex 当前拖放列的序号 * @param moveDownAndUp 向下移动 1 向上移动-1 */ const move = (startIndex: number, moveDownAndUp: MoveDownAndUp) => { // 获取当前拖动的内容 let newList = [...childList] let moveItem = newList.splice(startIndex, 1)[0]; newList.splice(startIndex + moveDownAndUp, 0, moveItem); setChildList(newList) }; /** 鼠标移动 */ const handleMouseMove = (event: React.MouseEvent) => { if (childList.length === 1) return let offset = event.pageY - startPageY; let draggingIndex = draggingItemIndex; //当移动的item没有超过list的长度, 则每往下移动超过lineHeight,就把数组中数据往后挪一位。相应的draggingItemIndex 和 startPageY都要增加一位。 if (offset > lineHeight && draggingIndex < childList.length - 1) { offset -= lineHeight; move(draggingIndex, MoveDownAndUp.down); setDraggingItemIndex(draggingIndex + MoveDownAndUp.down); setStartPageY(startPageY + lineHeight); //当移动的item还是list里面, 则每往上移动超过lineHeight,就把数组中数据往前挪一位。相应的draggingItemIndex 和 startPageY都要减少一位。 } else if (offset < -lineHeight && draggingIndex > 0) { offset += lineHeight; move(draggingIndex, MoveDownAndUp.up); setDraggingItemIndex(draggingIndex + MoveDownAndUp.up); setStartPageY(startPageY - lineHeight); } setOffsetY(offset); }; /** 拖动的样式 */ const getDraggingStyle = (index: number): React.CSSProperties => { if (index === draggingItemIndex) { return { transform: `translate(10px, ${offsetY}px)`, color: '#40a9ff', opacity: 0.8 }; } else { return {}; } }; const ListItem: React.FC = () => { return <> {childList.map((child: React.ReactElement, index: number) => (
1} onDragStart={(event: React.DragEvent) => onDragStart(event, index)} style={getDraggingStyle(index)} className={styles['list-item']}> {child}
))} } const CoverMask: React.FC = () => { return dragging ?
: <> } return (
); }; function attachPropertiesToComponent>( component: C, properties: P, ): C & P { const ret = component as any for (const key in properties) { if (Reflect.has(properties, key)) { ret[key] = properties[key] } } return ret } export default attachPropertiesToComponent(DragList, { Item: DragItem })

CSS 部分

.listContainer {
    position: relative;
  }
  
  
  .listContainer .coverMask {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(57, 243, 0, 0);
  }
  .list-item{
    background: #fff;
    min-height: 50px;
    padding: 0 10px;
    margin: 10px 0;
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-weight: bold;
    border-radius: 4px;
    &-title{
      padding: 0 10px;
      height: 100px;
      flex: 1;
      text-align: left;
    }
    &-delete{
      &:hover{
        fill: unset;
      }
    }
    &-move{
      cursor: pointer;
      &:hover{
        fill: unset;
      }
    }
    &:hover{
      &-delete{
          fill: unset;
      }
      color: #40a9ff;
    }
}
.header{
  display: flex;
  justify-content: flex-end;
  &-add{
      background: #40a9ff;
      width: 20px;
      height: 20px;
      display: flex;
      justify-content: center;
      align-items: center;
      color: #fff;
      transition: all;
      border-radius: 50%;
      .svg{
         fill: #fff; 
      }
  }
  &-close{
      background: #fff; 
      transition: all;
      transform: rotate(45deg);
      .svg{
          fill: #40a9ff; 
       }
  }
}
.drag-item{
  height: var(--drag-offest-height);
  overflow: auto;
  max-height: 500px;
  width: 100%;
}
  
  

你可能感兴趣的:(React +TS实现拖拽列表)