react-beautiful-dnd + react(Hooks) 实现二级拖拽目录式列表(开发记录)

1.任务需求

实现一个章节目录,要求章和章之间可以进行拖拽排序;每一章内的节可以拖拽排序,并且可以把其他章的小节拖拽到其他章中,章是不可以拖拽到其他章内的,章是可以新建的,小节是请求后端查询数据库返回的。示例如下
在这里插入图片描述
首先对技术做了调研,本着提高开发效率,能不自己造轮子就尽量找对应好用的库进行开发的理念上,我对react的相关的拖拽库进行了调研,其中包括react-draggable,react-sortable-hoc,react-beautiful-dnd等拖拽库,不谈其优劣,只谈适合业务场景,最终选择了react-beautiful-dnd这个拖拽库
react-beautiful-dnd英文github地址:https://github.com/atlassian/react-beautiful-dnd
react-beautiful-dnd中文github地址:https://github.com/chinanf-boy/react-beautiful-dnd-zh
react-beautiful-dnd库demo演示地址:https://react-beautiful-dnd.netlify.app/?path=/story/single-vertical-list–basic

文档比较简单,也不是那么易看,网上想搜索相关的例子也没有符合我的需求可供参考的,于是我就拉下了github的源码,去看了下源码,意外的发现,在源码中还有demo的源码,拉下源码后 yarn安装下依赖(不用我过多描述了)然后进入package.json中,找到storybook(那就启动吧,yarn storybook),神奇的一幕出现了,访问9002端口,这些demo就都出现了,然后顺藤摸瓜,找到demo的文件夹storybook,你就看到demo源码了
在这里插入图片描述

首先根据需求确定了一下这个数据结构如下图
在这里插入图片描述
这个基本用法文档都有说,我这里就不细说了,讲一下具体实现思路以及我的代码,根据文档描述
在这里插入图片描述DragDropContext作为最外层包裹住可以拖拽的部分
在这里插入图片描述
Droppable可以理解为一个拖拽域,里面的Draggable包裹的就是可拖拽元素,这些理解了之后对于当前这个场景就可以设计出这样的结构,以下是伪代码

<DragDropContext>
	<Droppable>
		<Draggable>
			{章}
			<Droppable>
				<Draggable>{节}</Draggable>
				<Draggable>{节}</Draggable>
				<Draggable>{节}</Draggable>
			</Droppable>
		</Draggable>
	</Droppable>
</DragDropContext>

一个这样的结构就出来的,再参考文档以及示例写出以下代码

 let defaultChapter: any = {
    id: '6',
    name: '默认章',
    node_list: [],
  }
 const [nodeList, setNodeList] = useState([defaultChapter])
  • 最外层
<DragDropContext onDragEnd={onDragEnd}>
   <div className={css.boardContain}>{board}</div>
</DragDropContext>
  • board
  const board = (
    <Droppable droppableId="board" type="COLUMN" direction="vertical">
      {(provided) => (
        <div ref={provided.innerRef} {...provided.droppableProps}>
          {nodeList?.map((node, index) => (
            <Column
              key={node?.name}
              index={index}
              title={node?.name}
              node={node}
            />
          ))}
          {provided.placeholder}
        </div>
      )}
    </Droppable>
  )
  • Column
const Column: React.FC<ColumnProps> = (props) => {
    const { title, index, node } = props

    return (
      <Draggable draggableId={title} index={index} key={title}>
        {(provided, snapshot) => (
          <div ref={provided.innerRef} {...provided.draggableProps}>
            <div
              // isDragging={snapshot.isDragging}
              {...provided.dragHandleProps}
              style={{
                marginBottom: '8px',
                marginTop: '8px',
                fontSize: '16px',
              }}
            >
              {/* {title} */}
              <Tooltip title="拖拽排序">
                <MenuOutlined
                  style={{
                    color: '#666',
                    marginRight: 5,
                  }}
                />
              </Tooltip>
              {index + 1}{' '}
              <Input
                style={{ width: 200, height: 32, fontSize: '16px' }}
                defaultValue={title}
                onChange={(e)=>chapterNameChange(e,index)}
              />
              {index !== 0 && (
                <CloseOutlined
                  color="#ccc"
                  size={10}
                  onClick={() => deleChapterBtnClick(index, node)}
                />
              )}
            </div>
            <ChapterList
              preIndex={index + 1}
              listId={title}
              listType="CHAPT"
              list={node.node_list}
            />
          </div>
        )}
      </Draggable>
    )
  }
  • ChapterList
const ChapterList: React.FC<ChapterProps> = (props) => {
    const { list, listId, listType, preIndex } = props

    return (
      <Droppable droppableId={listId} type={listType}>
        {(dropProvided, dropSnapshot) => (
          <div ref={dropProvided.innerRef} {...dropProvided.droppableProps}>
            {list?.map((li, index) => (
              <Draggable key={li.id} draggableId={li.id} index={index}>
                {(dragProvided, dragSnapshot) => (
                  <>
                    <div
                      ref={dragProvided.innerRef}
                      {...dragProvided.draggableProps}
                      key={li.id}
                      // isDragging={dragSnapshot.isDragging}
                    >
                      <div
                        {...dragProvided.dragHandleProps}
                        style={{ marginBottom: 13 }}
                      >
                        <Tooltip title="拖拽排序">
                          <MenuOutlined
                            style={{
                              color: '#666',
                              marginRight: 5,
                            }}
                          />
                        </Tooltip>
                        <span className={css.listItem}>
                          {' '}
                          {preIndex}.{index + 1} {li.name}
                        </span>
                      </div>
                    </div>
                  </>
                )}
              </Draggable>
            ))}
            {dropProvided.placeholder}
          </div>
        )}
      </Droppable>
    )
  }
//最外层要加上onDragEnd才能保存拖拽后的位置哦,主要逻辑其实就是拖拽的这个节或章替换掉目标的位置
const reorder = (
    list: any[],
    startIndex: number,
    endIndex: number,
  ): any[] => {
    const result = Array.from(list)
    const [removed] = result.splice(startIndex, 1)
    result.splice(endIndex, 0, removed)

    return result
  }
  
const onDragEnd = (result: any) => {
    const { source, destination, draggableId, type } = result
    console.log(
      'source',
      source,
      'destination',
      destination,
      'draggableId',
      draggableId,
      'type',
      type,
    )
    if (!destination) {
      return
    }
    if (
      source.droppableId === destination.droppableId &&
      source.index === destination.index
    ) {
      return
    }
    if (result.type === 'COLUMN') {
      const ordered = reorder(
        // this.state.ordered,
        nodeList,
        source.index,
        destination.index,
      )
      setNodeList([...ordered])
      return
    }
    if (result.type === 'CHAPT') {
      const sourceArr = nodeList.filter((n) => n.name == source.droppableId)[0]
        .node_list
      const desArr = nodeList.filter(
        (n) => n.name === destination.droppableId,
      )[0].node_list
      console.log(sourceArr, desArr)
      let moveItem = sourceArr.splice(source.index, 1)[0]
      desArr.splice(destination.index, 0, moveItem)
      console.log(sourceArr, desArr)
      return
    }
  }

最后

遇到的坑是数不胜数,印象最深刻的就是在最外层如果想让他溢出滚动的话,加overflow:auto是会出问题的,会导致新建的章的位置不被记录,改成overflow:overlay就解决了这个问题。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值