Как грамотно организовать код drag&drop в компоненте React, как использовать addEventListener и removeEventListener
Проблема в следующем: Имеется 30 компонентов AlphabetCard, с атрибутом draggaable = true. Внутри компонента две функции handlerDragStart и handlerDragEnd запускающиеся через соответственно onDrag и onDragEnd компонентов AlphabetCard. Функция handlerDragStart обращается к сторонней функции вне компонента add_and_remove_eventListener, которая через ссылку в пробрасываемых пропсах на 30 div background_matrix_circle добавляет методом addEventListener прослушку событий dragEnter, dragOver, dragLeave и drop, одноименные функции которых лежат вне компонента. handlerDragEnd соответственно снимает их методом removeEventListener. Всё хорошо работает, но... стоит перенести функции handlerDragEnter, handlerDragOver, handlerDragLeave, handlerDrop внутрь компонента AlphabetCard, метод removeEventListener перестаёт работать и у div-ов background_matrix_circle в браузере во вкладке события, начинают множиться прослушиватели событий, на каждом dragEnter и т.п. список div растет.
Тоже самое происходит при попытке пробросить атрибуты из компонента AlphabetCard в функцию handlerDragDrop. При присвоении domElem.addEventListener("drop", handlerDragDrop, false); // нужно добавить в handlerDragDrop аттрибуты, но что я только не перепробывал, и через bind и внешнюю доп функцию, removeEventListener этот обработчик после удалить не может.
Читал, что связка компонентов и элементов dom дерева браузера очень плохая практика, и видимо я этот узел пишу не в логике React, а по привычке на чистом js. По этому вопрос к бывалым разработчикам, как решали подобные задачи? Какова логика drag&drop в React?
const handlerDragEnter = (event)=> {
eventDefaultAndStopPropagation(event);
event.target.classList.add("drop_insert_border_on");;
}
const handlerDragOver = (event)=> {
eventDefaultAndStopPropagation(event);
}
const handlerDragLeave = (event)=> {
eventDefaultAndStopPropagation(event);
event.target.classList.remove("drop_insert_border_on");
}
const handlerDragDrop = (event) =>{
// !!! здесь нужно получить аттрибуты {backgroundLayerState,inputDataMatrixState} из AlphabetCard
eventDefaultAndStopPropagation(event);
event.target.classList.remove("drop_insert_border_on");
}
const add_and_remove_eventListener = (add,backgroundLayerState,inputDataMatrixState)=> {
const divElements = backgroundLayerState.children;
if (add) {
// Добавить обработчики событий
for(let domElem of divElements){
domElem.addEventListener("dragenter", handlerDragEnter, false);
domElem.addEventListener("dragover", handlerDragOver, false);
domElem.addEventListener("dragleave", handlerDragLeave, false);
domElem.addEventListener("drop",handlerDragDrop, false); // !!! добавить аттрибуты
}
} else {
// Удалить обработчики событий
for(let domElem of divElements){
domElem.removeEventListener("dragenter", handlerDragEnter, false);
domElem.removeEventListener("dragover", handlerDragOver, false);
domElem.removeEventListener("dragleave", handlerDragLeave, false);
domElem.removeEventListener("drop", handlerDragDrop, false);
}
}
}
function AlphabetCard({ props, draggableElemRef, inputDataMatrixState, backgroundLayerState }) {
console.log("AlphabetCard рендер");
const [transfer, setTransfer] = useState(false);
const thisRef = useRef(null);
const handlerDragStart = (event)=>{
if(transfer) return;
draggableElemRef.current = thisRef.current;
eventDefaultAndStopPropagation(event);
setTransfer(true);
add_and_remove_eventListener(true,backgroundLayerState,inputDataMatrixState);
console.log("dragStart");
};
const handlerDragEnd=(event)=> {
console.log("dragEnd");
eventDefaultAndStopPropagation(event);
add_and_remove_eventListener(false,backgroundLayerState,inputDataMatrixState);
setTransfer(false);
}
const circleInCard = props.value.split(" ").map((elem, index) => {
return <AlphabetCardCircle key={`alphabet_card_circle_${elem}_${index}`} props={elem} />;
});
return (
<div
ref={thisRef}
className={`alphabet_card grid_el_${props.index} ${props.addClass} ${transfer ? "drag_start_end" : ""}`}
data-value={props.value}
draggable="true"
onDragEnd={handlerDragEnd}
onDrag={handlerDragStart}
>
{circleInCard}
</div>
);
}