Drag and drop в списках

MUI (Material-UI) предоставляет широкий набор компонентов для построения интерфейсов, включая списки (List) и элементы списка (ListItem). Взаимодействие через drag and drop в таких списках часто требует интеграции с внешними библиотеками, поскольку MUI не содержит встроенной поддержки drag and drop. Наиболее популярные подходы используют библиотеку React DnD или react-beautiful-dnd. Рассмотрим организацию drag and drop на примере react-beautiful-dnd.


Установка необходимых зависимостей

Для реализации drag and drop потребуется установить библиотеку:

npm install @mui/material @emotion/react @emotion/styled react-beautiful-dnd

@mui/material и @emotion/* необходимы для работы MUI, а react-beautiful-dnd отвечает за функционал перетаскивания.


Структура компонентов

Основные компоненты для drag and drop:

  1. DragDropContext – оборачивает область, где будет происходить перетаскивание.
  2. Droppable – контейнер, в который можно перемещать элементы.
  3. Draggable – элемент, который можно перемещать.

Пример базовой структуры:

import React, { useState } from 'react';
import { List, ListItem, ListItemText, Paper } from '@mui/material';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

const initialItems = [
  { id: '1', text: 'Элемент 1' },
  { id: '2', text: 'Элемент 2' },
  { id: '3', text: 'Элемент 3' },
];

function DraggableList() {
  const [items, setItems] = useState(initialItems);

  const handleOnDragEnd = (result) => {
    if (!result.destination) return;

    const reorderedItems = Array.from(items);
    const [removed] = reorderedItems.splice(result.source.index, 1);
    reorderedItems.splice(result.destination.index, 0, removed);

    setItems(reorderedItems);
  };

  return (
    <DragDropContext onDrag End={handleOnDragEnd}>
      <Droppable droppableId="droppable-list">
        {(provided) => (
          <List
            component={Paper}
            {...provided.droppableProps}
            ref={provided.innerRef}
          >
            {items.map((item, index) => (
              <Draggable key={item.id} draggableId={item.id} index={index}>
                {(provided, snapshot) => (
                  <ListItem
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                    sx={{
                      backgroundColor: snapshot.isDragging ? 'grey.300' : 'background.paper',
                      mb: 1,
                    }}
                  >
                    <ListItemText primary={item.text} />
                  </ListItem>
                )}
              </Draggable>
            ))}
            {provided.placeholder}
          </List>
        )}
      </Droppable>
    </DragDropContext>
  );
}

export default DraggableList;

Ключевые моменты реализации

  • handleOnDragEnd – функция, вызываемая после завершения перетаскивания. Именно здесь обновляется состояние списка.
  • provided.placeholder – важный элемент, который сохраняет пространство для перетаскиваемого элемента, предотвращая визуальные «скачки» при перемещении.
  • snapshot.isDragging – состояние перетаскиваемого элемента. Можно использовать для динамического изменения стиля элемента при drag.

Стилизация элементов при перетаскивании

Использование sx пропсов MUI позволяет легко менять фон, тени и другие свойства элементов во время drag:

sx={{
  backgroundColor: snapshot.isDragging ? 'grey.300' : 'background.paper',
  boxShadow: snapshot.isDragging ? 3 : 0,
  mb: 1,
}}

Эти параметры повышают визуальную читаемость интерфейса и дают пользователю ясное понимание, какой элемент в данный момент перемещается.


Множественные списки с drag and drop

Для реализации нескольких списков, между которыми можно перетаскивать элементы, используется несколько Droppable:

<Droppable droppableId="list-1">
  {...}
</Droppable>
<Droppable droppableId="list-2">
  {...}
</Droppable>

При обработке onDragEnd проверяется source.droppableId и destination.droppableId для определения, перемещается ли элемент внутри одного списка или между разными списками.


Оптимизация производительности

  • Минимизировать количество лишних перерисовок элементов, используя React.memo для ListItem.
  • Не создавать функции inline внутри Draggable и Droppable, лучше использовать заранее определённые функции или методы.
  • При больших списках можно использовать виртуализацию через react-window совместно с drag and drop, чтобы улучшить производительность.

Дополнительные возможности

  • Автоскролл при drag: react-beautiful-dnd автоматически поддерживает прокрутку контейнера при перемещении элемента к краю.
  • Динамическая высота элементов: библиотека корректно обрабатывает элементы разной высоты, сохраняя плавность drag.
  • Анимация перемещения: встроенные анимации делают интерфейс плавным без дополнительного кода.

Эта методика позволяет создавать гибкие и наглядные интерфейсы списков с drag and drop в MUI, обеспечивая высокую визуальную читаемость и удобство использования.