引言
拖放可能看起来像一个简单的用户交互,其中你拾取一个项目并将其放置在其他地方,类似于在Trello板或任何看板样式界面上组织项目,其中卡片或信息可以轻松通过点击和拖动进行重新排列。
然而,整个过程背后隐藏着许多复杂性。从在不同部分之间移动数据开始,到获取正确的放置位置。当你有一个可以跨多个级别移动的嵌套元素层时,这个挑战就会升级。
为了实现这一点,我们无需在这里重新发明轮子,我们有几个库可供选择。最流行的几个是:
react-beautiful-dnd
脱颖而出,是最常用的,它提供了一个干净且高级的API,具有很多抽象。它是由Atlassian开发的。react-dnd
功能强大但略显复杂,需要一些时间来适应。dnd-kit
是最新的之一,它是现代、轻量级且性能良好的。
问题**
在epilot,我们在应用程序的不同部分广泛使用了react-beautiful-dnd
。
然而,在尝试一些复杂的场景时,我们在某些情境中遇到了一些障碍,它无法准确预测元素的放置位置。这里是一个例子。
我们为特定的边缘情况设计了几种解决方案,但它们无法解决所有问题。由于这些边缘情况,基于拖放位置移动元素数据的代码变得混乱不堪。react-beautiful-dnd
停止维护和支持也不利于继续使用它的理由。
解决方案
最终,我们决定探索能够以更明确、直观和简单的API解决问题的替代库。在评估了几个选项后,我们选择了dnd-kit
,因为它提供了一个明确而简单的API。一个附加的好处是它还提供了hooks API,而一些旧库中则缺少此功能。
dnd-kit
的关键优势包括:
- 零依赖
- 优化的性能
- 可访问性
- 支持多种输入方法
- 全面的文档和示例
演示代码
这里是使用两个库进行简单拖放的代码。
代码语言:jsx复制// react-beautiful-dnd
export const DragDrop = ({ initialData }) => {
const [items, setItems] = useState(initialData);
const onDragEnd = () => { /** 移动数据 **/ };
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided) => (
<ul {...provided.droppableProps} ref={provided.innerRef}>
{items.map((item, index) => (
<DraggableItem item={item} key={item.id} index={index} />
))}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
);
};
const DraggableItem = ({ item, index }) => (
<Draggable draggableId={item.id} index={index}>
{(provided) => (
<li
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
{item.content}
</li>
)}
</Draggable>
);
// dnd-kit
export const DndKit = ({ initialData }) => {
const [items, setItems] = useState(initialData);
const onDragEnd = () => { /** 移动数据 **/ };
const sensors = useSensors(useSensor(PointerSensor));
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
<ul>
{items.map((item) => (
<DraggableItem item={item} key={item.id} />
))}
</ul>
</SortableContext>
</DndContext>
);
};
const DraggableItem = ({ item }) => {
const {
attributes,
isDragging,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({
id: item.id,
});
const style = {
transform: CSS.Transform.toString(transform),
transition,
cursor: isDragging ? "grabbing" : "grab",
};
return (
<li
{...attributes}
{...listeners}
key={item.id}
data-dnd-id={item.id}
data-dnd-type="item"
ref={setNodeRef}
style={style}
>
{item.content}
</li>
);
};
在这两个具有相同功能的代码示例中,你可以看出dnd-kit
的复杂性更高,但也更全面。它使用Sortable来解决这个问题,因为这是它解决的用例之一。它符合我们解决更复杂的嵌套拖放场景以及在不同级别拖动的能力的目标。
dnd-kit
的一些关键优势非常有用,包括:
- 自定义占位符 - 这是最受欢迎的用例之一。在
react-beautiful-dnd
中实现这一点可能会很具有挑战性,因为它将具有有限的功能并且需要大量的JavaScript来达到令人满意的状态。在我们的场景中,我们希望在拖动期间显示元素及其子元素的精简版本,因
此我们使用了带有React portal的DragOverlay。
- 碰撞检测策略 - 它提供了针对不同用例特定的各种策略,从而在树内控制元素切换。你甚至可以开发自己的自定义策略以满足你的要求。
- 排序策略 - 同样,它提供了不同的排序策略,使垂直列表、水平列表或网格的排序成为可能。
我正在参与2024腾讯技术创作特训营第五期有奖征文,快来和我瓜分大奖!