使用react-dnd编写一个可拖拽排列的list
作者:利器之路
今天的需求来啦:做一个可以通过拖拽排列的list。最终效果是这样:
开始之前,我找了个市面上比较主流的工具库,做个比对:
这个是atlassian用来开发jira看板功能的库,有动画效果,很漂亮,确实对得起这个包的名字。优点是相对易用,而且好看,缺点是仅适合开发list一类的组件,而且几年前就已经不再维护了,对于新版的函数式react可能不太兼容。
- dnd-kit这个是atlassian在弃用
react-beautiful-dnd
后重新维护的一个版本,效果是一样的美丽,如果是web端的,还是很推荐的。 - reat-dnd这是个比较全面的dnd库,可以实现各种功能,要注意的是这是基于H5的开发,如果你开发的端不是H5,可能不适用。
react-dnd
的官方文档介绍了如何做一个棋盘,类似的,这篇文章将介绍如何用react-dnd
做一个draggable
list。
Draggable
首先将list中的每一个item做成draggable
组件。
引入useDrag
:
// ListItem.tsx import { styled } from '@mui/material/styles'; import * as React from 'react'; import { useDrag } from 'react-dnd'; const StyledListItem = styled('div', { shouldForwardProp: prop => prop !== 'isDragging' })<{ isDragging: boolean }>(({ isDragging, theme }) => ({ opacity: isDragging ? 0.5 : 1, border: '1px blue solid', })); const ListItem = () => { const [{isDragging}, dragRef] = useDrag(() => ({ type: 'listItem', collect: monitor => ({ isDragging: !!monitor.isDragging(), }), })) return ( <StyledListItem ref={dragRef} isDragging={isDragging}> list item </StyledListItem> ) } export default ListItem;
这样我们在demo中就能看到,这个组件就可以用鼠标拖了:
Droppable
我们再将list容器做成droppable
组件,这样draggable
组件才可以放在droppable
的list里。
引入useDrop
:
// SortableList.tsx import React, { forwardRef } from 'react'; import { useDrop } from 'react-dnd'; import ListItem from './components/listItem'; const SortableList = () => { const canMoveItem = () => { console.log('canMoveItem') return true; //<--- 具体逻辑后面再写,这里先设为true,即容器内部一直是可以drop的。 } const moveItem = () => { console.log('moveItem'); //<--- 具体逻辑后面再写。 } const [{ isOver, canDrop }, dropRef] = useDrop( () => ({ accept: 'listItem', canDrop: () => canMoveItem(), drop: () => moveItem(), collect: (monitor) => ({ isOver: !!monitor.isOver(), canDrop: !!monitor.canDrop() }) }), [canMoveItem, moveItem] ) return ( <div> isOver: {String(isOver)}<br /> canDrop: {String(canDrop)} <div ref={dropRef} style={{ position: 'relative', width: '600px', height: '400px', border: '2px purple solid' }} > <ListItem></ListItem> </div> </div> ); } export default SortableList;
这样页面上,我再容器内部拖动时,isOver
为true
,canDrop
为true
,拖到容器外isOver
就变成false
了:
既是draggable,又是droppable
当有多个list item,可以通过拖拽调整顺序,这样我需要将list item也变成droppable
:
// ListItem.tsx // 将list item也变成droppable const [spec, dropRef] = useDrop({ accept: 'listItem', hover: (item, monitor) => { const dragIndex = item.index const hoverIndex = index const hoverBoundingRect = ref.current?.getBoundingClientRect() const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2 const hoverActualY = monitor.getClientOffset().y - hoverBoundingRect.top // 如果是向下拖,只有当hover的坐标小于自身高度的一半时,继续保持drag,否则相当于要挪动其他的list item,给当前的item腾个地方 // if dragging down, continue only when hover is smaller than middle Y if (dragIndex < hoverIndex && hoverActualY < hoverMiddleY) return // 如果是向上拖,只有当hover的坐标大于自身高度的一半时,继续保持drag,否则相当于要挪动其他的list item,给当前的item腾个地方 if (dragIndex > hoverIndex && hoverActualY > hoverMiddleY) return moveListItem(dragIndex, hoverIndex) item.index = hoverIndex }, });
那么如何让一个list item既是draggable
,又是droppable
呢?用以下方式:
// ListItem.tsx const ref = useRef(null); const dragDropRef = dragRef(dropRef(ref))
重排list
上一步我们把moveListItem
传给了父组件,那么在父组件中,我们可以这样来改变list的顺序:
// Mock data const PETS = [ { id: 1, name: 'dog' }, { id: 2, name: 'cat' }, { id: 3, name: 'fish' }, { id: 4, name: 'hamster' }, ]
// SortableList.tsx const moveListItem = useCallback( (dragIndex, hoverIndex) => { console.log({dragIndex, hoverIndex}) const dragItem = pets[dragIndex] const hoverItem = pets[hoverIndex] // 将PETS数组中,drag的item和hover的item交换位置 setPets(pets => { const updatedPets = [...pets] updatedPets[dragIndex] = hoverItem updatedPets[hoverIndex] = dragItem return updatedPets }) }, [pets], )
这样我们就能得到这样的效果:
这样一个sortable list就大功告成啦!
总结
现在这个list是没有任何动画的,要是listItem
在移动的过程中是有个过渡效果的,就显得丝滑多了,那么接下来我将用react-flip-move
将动画效果加到每个item中。
到此这篇关于使用react-dnd编写一个可拖拽排列的list的文章就介绍到这了,更多相关react-dnd可拖拽排列的list内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!