Shopify Draggable — это библиотека для управления перетаскиванием элементов в DOM, которая предоставляет высокий уровень гибкости, включая поддержку drag-and-drop, sortable списков и контейнеров с несколькими зонами перетаскивания. В контексте React ключевым аспектом является синхронизация состояния компонентов с событиями Draggable, поскольку библиотека напрямую работает с DOM, а React использует виртуальный DOM. Несогласованность этих слоев может привести к багам, таким как «мерцающие» элементы или некорректное обновление списка после сортировки.
Для корректной работы чаще всего используют рефы
(useRef) для привязки Draggable к реальным
DOM-элементам, а также эффекты (useEffect)
для инициализации и очистки экземпляра Draggable.
import { useEffect, useRef } from 'react';
import { Draggable, Sortable } from '@shopify/draggable';
function SortableList({ items, onUpdate }) {
const containerRef = useRef(null);
useEffect(() => {
const sortable = new Sortable(containerRef.current, {
draggable: '.item'
});
sortable.on('sortable:stop', (event) => {
const newIndex = event.newIndex;
const oldIndex = event.oldIndex;
onUpdate(oldIndex, newIndex);
});
return () => sortable.destroy();
}, [onUpdate]);
return (
<div ref={containerRef}>
{items.map((item, index) => (
<div key={item.id} className="item">{item.content}</div>
))}
</div>
);
}
Sortable и Draggable в ReactDraggable предоставляет низкоуровневый
API для управления событиями перетаскивания, а
Sortable — высокоуровневый инструмент для сортируемых
списков. В React обычно используют Sortable, так как он
автоматически обрабатывает порядок элементов и события drag-and-drop.
Основные события для интеграции:
sortable:start — начало перетаскиванияsortable:sort — изменение позиции во время dragsortable:stop — завершение перетаскиванияsortable:updated — окончательная смена порядка
элементовДля корректного обновления состояния компонента важно внутри обработчика событий изменять только состояние React, а не напрямую DOM. Это позволяет сохранить совместимость с виртуальным DOM.
sortable.on('sortable:stop', (event) => {
const updatedItems = Array.from(items);
const [movedItem] = updatedItems.splice(event.oldIndex, 1);
updatedItems.splice(event.newIndex, 0, movedItem);
setItems(updatedItems);
});
React-компоненты часто рендерят динамические списки, которые меняются в процессе работы. В таких случаях необходимо:
Sortable.key для элементов списка,
чтобы React корректно отслеживал позиции и предотвращал пересоздание
элементов.useEffect(() => {
const sortable = new Sortable(containerRef.current, {
draggable: '.item'
});
return () => sortable.destroy();
}, [items.length]); // пересоздание при изменении числа элементов
Draggable поддерживает группы
(containers), что позволяет перетаскивать элементы
между несколькими списками. В React это реализуется через
несколько ref:
const containerRefs = useRef([]);
useEffect(() => {
const sortable = new Sortable(containerRefs.current, {
draggable: '.item',
mirror: {
constrainDimensions: true
}
});
sortable.on('sortable:stop', (event) => {
const sourceIndex = event.oldIndex;
const targetIndex = event.newIndex;
onMoveBetweenLists(event.source, event.over, sourceIndex, targetIndex);
});
return () => sortable.destroy();
}, [lists]);
Особенности:
mirror.constrainDimensions позволяет сохранить размеры
элемента при переносе.event.source и event.over
помогает определить источник и цель перетаскивания между списками.Draggable поддерживает создание зеркальных элементов
(mirror) при перетаскивании. В React анимации чаще
всего настраиваются через CSS или через состояние, которое меняется при
событиях Draggable:
.item {
transition: transform 0.2s ease;
}
sortable.on('sortable:sort', (event) => {
const { dragEvent } = event;
event.source.style.transform = `translate(${dragEvent.delta.x}px, ${dragEvent.delta.y}px)`;
});
Это позволяет синхронизировать визуальное перемещение с виртуальным DOM, предотвращая конфликты между React и библиотекой.
При больших списках важно:
React.memo или
PureComponent для элементов списка.useCallback, чтобы
избежать лишних подписок на события.const handleUpdate = useCallback((oldIndex, newIndex) => {
setItems((prevItems) => {
const updated = [...prevItems];
const [moved] = updated.splice(oldIndex, 1);
updated.splice(newIndex, 0, moved);
return updated;
});
}, []);
Draggable легко комбинируется с другими компонентами React, включая формы, кнопки и сложные карточки. Важно:
click, input) внутри карточки.<div ref={ref} className="draggable-card">
<input type="text" value={value} onCha nge={onChange} />
</div>
ref для контейнера и
useEffect для инициализации Draggable.Sortable для списков, Draggable
для низкоуровневых операций.key для элементов списка, чтобы React
корректно отслеживал позиции.refs.Такой подход обеспечивает полное соответствие React и Shopify Draggable, позволяя создавать высоко интерактивные интерфейсы без конфликтов между виртуальным DOM и прямыми манипуляциями с DOM.