树形组件拖拽写法思路

树形结构的生成,可以通过递归树形数据遍历而成

节点的思路

const  Node=({ children })=>{
       return (
         
// 展开收起的三角形图标 // 节点的图标
自定义节点内容
// 子节点 { children && (
{children}
) }
) }

children-container 需要加个padding-left的样式缩进,

所以写出来后用法就如下所示,可以无限嵌套,形成基本的react 的组件解构

  
          
          
          
          
              
          
  

拖拽的思路:

  1. 一开始打算使用react-dnd组件,但是发现学习成本太高,而且文档虽然有,但是感觉还是不详细,其实只需要一个简单的拖拽调位就能满足项目需求。
  2. 于是决定手动写拖拽,先实现一个 DragWrapper 组件,用于包裹节点(或者直接替换节点div),使节点能拖拽
  3. 假设过程是,拖拽的节点称为startNode, 拖拽到endNode上释放鼠标,

拖拽容器:

{children}

这个draggable的值不能是true或者false,必须得是"true"和"false";
先是onDragStart
onDragStart: 通过e.dataTransfer.setData,传递一个id,是startNode的id,用于后面的数据操作

const handleDragStart = e => {
        e.stopPropagation();
        // startNode的id
        e.dataTransfer.setData('guid', node.guid);
 };

第二步是onDrop,要注意这里获取到的node.id是endNode的id,同一个组件函数里面,通过props获取的node.guid 是不一样的,因为渲染上去,这就是两个组件,onDragStart在一个节点里触发,onDrop是在另一个节点里触发的,这两个节点都是用同一个函数组件生成的,两个节点之间通过 e.dataTransfer.setData,把起始节点的guid传过来,在end节点里收取,

const handleDrop = e => {
        e.stopPropagation();
        const startGuid = e.dataTransfer.getData('guid');
        const endGuid = node.guid;
        if(startGuid!==endGuid){
            1. 在树形数据里,把startNode找到,先用一个变量存起来,然后在树里面删除
            2. 在把endNode找到,往后面插入startNode,即存起来的变量,
            3. 更新树,重新存入redux中
        }
}

可以理解成同一个函数生成的两个实例,一个start触发了另一个的end。有了startGuid和endGuid,就好操作了,

方便操作,可以写一个通用方法loop,

通过loop的第三个参数arr,我们找到了对应的id的节点后,这个arr就是这个节点所在的数组,往后面插入节点就是 arr.splice(i+1 , 0 , node);往前面插入节点就是arr.splice(i , 0 , node)

const loop = (data, key, callback) => {
    data.forEach((item, index, arr) => {
        if (item.guid === key) {
            callback(item, index, arr);
            return;
        }
        if (item.children) {
            loop(item.children, key, callback);
        }
    });
};

有两个id,以及一棵树的数据,直接用js操作数据,对树节点进行删除增加即可,

位置判断

过程:

  1. 自己想没什么思路,参考了一下rc-tree

rc-tree 目前用的是dropPosition,值分别为 -1,0,1,代表节点塞到上方,塞到下方,塞到里面,
判断函数的地址 https://github.com/react-component/tree/blob/master/src/util.tsx

可以看到这是判断位置的一个条件


image.png

e 是onDrop的参数,clientY 可以通过e获取到,clientY是释放鼠标的时候,鼠标相对浏览器左上角的纵坐标,获取这个y坐标,就可以得到鼠标距离浏览器顶部的距离,然后通过e.target.getBoundingClientRect(),可以得到endNode的宽高,以及top(节点距离浏览器最顶部的距离)

clientY < (top + height/2) 说明鼠标在节点的中间还偏上。
这里是拿节点的垂直中间位置去跟鼠标位置对比,可以根据这个,判断startNode是插入在endNode的上方还是下方

所以我们可以在onDragOver的时候,判断位置,设置样式,加一条红线,至少让用户能看到这个节点即将要插入的位置,在onDragLeave的时候删除样式

最后使用

可以用拖拽组件DragWrapper把节点包裹

const  Node=({ children })=>{
       return (
         
// 展开收起的三角形图标 // 节点的图标
自定义节点内容
// 子节点 { children && (
{children}
) }
) }

为什么不包裹整个node节点,而只包裹渲染的那部分。因为当节点展开的时候,子节点的拖拽,也会被认为是父节点的拖拽,所以只包裹渲染的部分即可

注意点:

有 canDrag这个函数可以根据业务需求判断是否允许拖拽,但是dom节点即便设置了 draggable="false" ,或者直接不设置draggable,在极限情况下,能卡出拖拽,拖拽的是文本内容
需要给这个节点加上css,不允许选中文字

       -moz-user-select: -moz-none;
        -khtml-user-select: none;
        -webkit-user-select: none;
        /*
          Introduced in IE 10.
          See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/
        */
        -ms-user-select: none;
        user-select: none;

你可能感兴趣的:(树形组件拖拽写法思路)