Работа с React

Dragula — это легковесная библиотека для реализации drag-and-drop взаимодействий в веб-приложениях. Её ключевая особенность — простота интеграции с любыми структурами DOM и минимальная конфигурация. Dragula ориентирована на прямую работу с элементами и контейнерами, предоставляя возможность перетаскивать элементы между ними или внутри одного контейнера без сложных настроек.

Контейнеры и элементы: Dragula оперирует двумя основными понятиями:

  • Контейнер — блок, внутри которого находятся элементы для перетаскивания.
  • Элемент — любой DOM-элемент, который может быть перетащен.

Установка и интеграция с React

Dragula доступна через npm и может быть подключена в проект React следующим образом:

npm install dragula

Для корректной работы необходимо импортировать стили Dragula:

import 'dragula/dist/dragula.css';
import dragula from 'dragula';

Инициализация Dragula в React

В React Dragula рекомендуется использовать внутри useEffect, чтобы гарантировать наличие DOM-элементов перед созданием экземпляра drag-and-drop. Классическая схема инициализации выглядит так:

import React, { useRef, useEffect } from 'react';
import dragula from 'dragula';
import 'dragula/dist/dragula.css';

function DragContainer() {
    const containerRef = useRef(null);

    useEffect(() => {
        const drake = dragula([containerRef.current]);

        return () => drake.destroy();
    }, []);

    return (
        <div ref={containerRef}>
            <div className="item">Элемент 1</div>
            <div className="item">Элемент 2</div>
            <div className="item">Элемент 3</div>
        </div>
    );
}

Особенности:

  • useRef обеспечивает прямой доступ к DOM-узлу контейнера.
  • useEffect гарантирует создание экземпляра Dragula после рендера.
  • drake.destroy() очищает подписки и предотвращает утечки памяти.

Конфигурация Dragula

Dragula предоставляет множество опций для управления поведением перетаскивания:

const drake = dragula([containerRef.current], {
    moves: (el, source, handle) => handle.classList.contains('handle'),
    accepts: (el, target) => !target.classList.contains('no-drop'),
    revertOnSpill: true,
    removeOnSpill: false,
    copy: false
});

Разбор ключевых опций:

  • moves — определяет, какие элементы могут быть перетащены и с помощью каких «ручек».
  • accepts — позволяет запрещать сброс элементов в определенные контейнеры.
  • revertOnSpill — возвращает элемент на исходное место при неудачном перетаскивании.
  • removeOnSpill — удаляет элемент, если он был «уронен» вне контейнера.
  • copy — позволяет копировать элемент вместо перемещения.

Работа с событиями

Dragula поддерживает широкий набор событий, которые помогают отслеживать жизненный цикл перетаскивания:

drake.on('drag', (el) => {
    console.log('Начало перетаскивания:', el);
});

drake.on('drop', (el, target, source, sibling) => {
    console.log('Элемент сброшен:', el);
    console.log('Целевой контейнер:', target);
    console.log('Исходный контейнер:', source);
    console.log('Элемент перед которым был сброшен:', sibling);
});

drake.on('over', (el, container) => {
    console.log('Элемент над контейнером:', container);
});

drake.on('out', (el, container) => {
    console.log('Элемент покинул контейнер:', container);
});

Ключевой момент: все события Dragula работают с нативными DOM-элементами. Для синхронизации состояния в React необходимо вручную обновлять состояние компонента на основе этих событий.

Интеграция с состоянием React

Чтобы Dragula корректно взаимодействовала со стейтом React, используется подход «синхронизации после drop»:

const [items, setItems] = React.useState(['Элемент 1', 'Элемент 2', 'Элемент 3']);

useEffect(() => {
    const drake = dragula([containerRef.current]);

    drake.on('drop', (el, target, source, sibling) => {
        const newOrder = Array.from(containerRef.current.children).map(child => child.textContent);
        setItems(newOrder);
    });

    return () => drake.destroy();
}, []);

Такой подход гарантирует, что состояние React всегда соответствует текущему порядку элементов в DOM, избегая рассинхронизации между виртуальным DOM и реальным.

Работа с несколькими контейнерами

Dragula позволяет одновременно перетаскивать элементы между несколькими контейнерами. Для этого достаточно передать массив контейнеров при инициализации:

const drake = dragula([containerRef1.current, containerRef2.current]);

Можно управлять, какие элементы можно перемещать между контейнерами с помощью функции accepts:

accepts: (el, target) => target !== containerRef2.current || el.classList.contains('movable')

Использование кастомных «ручек» для перетаскивания

Чтобы сделать перетаскивание более гибким, Dragula позволяет выделить конкретные зоны элементов, через которые осуществляется перемещение:

moves: (el, source, handle) => handle.classList.contains('drag-handle')

Такой метод позволяет создавать интерфейсы с кнопками, текстовыми зонами и другими интерактивными элементами без риска случайного перетаскивания.

Стилизация и визуальные эффекты

Dragula автоматически добавляет классы во время перетаскивания:

  • gu-transit — элемент в процессе перемещения.
  • gu-mirror — зеркальная копия элемента, которую тянет курсор.
  • gu-hide — скрытие исходного элемента при копировании.
  • gu-unselectable — предотвращает выделение текста.

Для визуальных эффектов рекомендуется использовать CSS-переходы, чтобы обеспечить плавность перемещения и подсветку целевых контейнеров.

Особенности работы в React с повторными рендерами

Dragula работает напрямую с DOM, поэтому повторные рендеры React могут сломать связь между элементами и контейнерами. Для предотвращения проблем:

  • Пересоздавать экземпляр Dragula после изменения количества контейнеров.
  • Использовать key в списках элементов для точной идентификации DOM-узлов.
  • Очищать предыдущие экземпляры drake.destroy() перед повторной инициализацией.

Поддержка динамического контента

Если элементы добавляются или удаляются динамически, необходимо вызывать drake.containers.push(newContainer) или пересоздавать экземпляр Dragula для новых контейнеров. Это обеспечивает правильное поведение перетаскивания и предотвращает ошибки с недоступными элементами.