应用场景
实现以上效果,在拖动排序上悬浮呈现可拖动状态,在其他地方悬浮无法拖动;当拖动(拖动排序时)整个组件一起被拖动。
实现拖动排序
//index.tsx
import Card from './Card.tsx'
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
const listData=[
{id:1,zoneName:'区域一'},
{id:2,zoneName:'区域二'}
]
function dnd(){
return (
<DndProvider backend={HTML5Backend}>
{
listData.map((card, i) => (
<Card
key={card.id}
index={i}
id={card.id}
text={card.zoneName}
/>
))
}
</DndProvider>
)
}
//Card.tsx
import { forwardRef, useImperativeHandle, useRef } from 'react';
const Card = forwardRef(function Card({ id, text, isDragging, connectDragSource, connectDropTarget, connectDragPreview }, ref) {
const elementRef = useRef(null);
const dragbleRef = useRef(null);
connectDragSource(elementRef);//显示可以拖拽的(按钮)组件
connectDragPreview(dragbleRef)//被实际拖拽的(整体)组件
connectDropTarget(elementRef); //被放置的组件
useImperativeHandle(ref, () => ({
getNode: () => elementRef.current,
}));
return (
<div ref={dragbleRef} >
<span ref={elementRef} style={{cursor: 'move'}}>拖动排序</span>
<div>{{id}}{{text}}不能显示可以拖动哦</div>
</div>);
});
// DropTarget()(DragSource()(card))
export default DropTarget(ItemTypes.CARD, {
hover(props, monitor, component) {
if (!component) {
return null;
}
// props:组件当前的props
// monitor:当前拖拽状态(item,type,offsets,dropped?,,,)
// component:当前组件实例Card(每个可拖动的元素)
const node = component.getNode();
if (!node) {
return null;
}
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
if (dragIndex === hoverIndex) {
return;
}
// Card的节点边界
const hoverBoundingRect = node.getBoundingClientRect();
// Card高度的一半
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
// 鼠标的坐标
const clientOffset = monitor.getClientOffset();
// 鼠标距离Card的上边界距离
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
// 往下拖动时,鼠标还没超过悬浮元素自身高度的一半就当作拖动失败
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
// 往上拖动
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
props.moveCard(dragIndex, hoverIndex);
// 排序
monitor.getItem().index = hoverIndex;
},
}, (connect) => ({
connectDropTarget: connect.dropTarget(),
}))(DragSource(ItemTypes.CARD, {
beginDrag: (props) => ({
id: props.id,
index: props.index,
}),
}, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging(),
}))(Card));
代码分析
☝️:DndProvider组件一定要包裹在拖动组件外侧
☝️:DropTarget()(DragSource()(Card))是js中高阶组件的写法,DropTarget,DragSource这两个函数都包括三个参数 type, spec, collect。这可以看到最后传递的参数是Card,那么 type, spec, collect这几个参数的作用显然是为了把返回值作为props传递到Card中。
☝️:从 type, spec, collect这几个参数中获取到的三个东西比较重要,分别是拖拽的按钮,被拖拽的整体以及被放入的目标组件,他们使用ref与dom进行绑定:
const elementRef = useRef(null);
const dragbleRef = useRef(null);
connectDragSource(elementRef);//显示可以拖拽的(按钮)组件
connectDragPreview(dragbleRef)//被实际拖拽的(整体)组件
connectDropTarget(elementRef); //被放置的组件
以上代码只是实现的大概思路,可以看看文章开头的两个友情链接,很不错。