Примечание: Щелкните здесь, для прототипа без использования react-hooks, который использует старую версию react-dnd, в которой HOC используется вместо react-hooks.

Перетаскивание - одна из замечательных функций HTML5. Раньше мы полагались на события мыши в javascript для выполнения перетаскивания. Но HTML5 упростил рабочий процесс. Но заставить перетаскивание работать в среде React сложно. Потому что может возникнуть конфликт между прямым манипулированием DOM и виртуальным DOM, поддерживаемым React. Здесь на сцену выходит react-dnd, простая и понятная библиотека, которая помогает нам работать с перетаскиванием HTML5 в среде реакции.

Код, используемый в этом руководстве, доступен по адресу https://gist.github.com/rethna2/83afd1671ef7932e898d02f67f545e6e.

Также вы можете проверить это в расположенной ниже codeandbox.



Для начала нам нужно добавить две библиотеки. (Я упомянул версию, в которой разработан этот прототип)

$ npm install [email protected] [email protected]
  1. react-dnd предоставляет всю логику и
  2. react-dnd-html5-backend связывает логику с API перетаскивания html5.

Есть три узла dom или компонент jsx, которые нужно обработать для завершения рабочего процесса.

  1. перетаскиваемые объекты. В нашем примере с канбан-доской каждый элемент задачи является перетаскиваемым объектом. Предметы, которые мы можем таскать.
  2. Выпадающие контейнеры. Каждый столбец на доске канбан представляет собой выпадающий контейнер. Мы можем перетащить любой элемент задачи в этот контейнер.
  3. Контекст перетаскивания: оболочка, которая включает в себя все перетаскиваемые объекты и перетаскиваемые контейнеры. Это необходимо для установки границы и инициализации сцены. Можно использовать несколько перетаскиваний, имея несколько неперекрывающихся контекстов.

Контекст предоставляется DndProvider, и мы используем хуки для предоставления перетаскиваемого интерфейса. Наш импорт выглядит так, как показано ниже. Мы используем перехватчики реакции вместо компонента класса реакции.

import React, { useState, useCallback, useRef } from "react";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";

Доска представлена ​​приведенной ниже структурой. Это может показаться немного сложным, но позвольте мне выделить некоторые моменты.

<DndProvider backend={HTML5Backend}>
  <section style={classes.board}>
    {channels.map(channel => (
      <KanbanColumn key={channel} status={channel} changeTaskStatus={changeTaskStatus}>
        <div style={classes.column}>
          <div style={classes.columnHead}>{labelsMap[channel]}</div>
          <div>
          {tasks.filter(item => item.status === channel)
            .map(item => (
              <KanbanItem key={item._id} id={item._id}>
                <div style={classes.item}>{item.title}</div>
              </KanbanItem>
          ))}
       </div>
     </div>
    </KanbanColumn>
  ))}
  </section>
</DndProvider>

У нас есть двухуровневый вложенный цикл dom с использованием Array.map, первый отображает столбцы доски канбан, а второй отображает элементы задач. Также обратите внимание, что есть два компонента, о которых я расскажу раньше. Также обратите внимание, что все заключено внутри DndProvider.

Отбрасываемый контейнер

Компонент столбца канбан-доски представлен ниже.

const KanbanColumn = ({ status, changeTaskStatus, children }) => {
  const ref = useRef(null);
  const [, drop] = useDrop({
    accept: "card",
    drop(item) {
      changeTaskStatus(item.id, status);
    }
  });
  drop(ref);
  return <div ref={ref}> {children}</div>;
};

Хук useDrop выполняет необходимую логику для перетаскивания задач в столбец. Компонент KanbanColumn не содержит пользовательского интерфейса, вместо этого он передается как дочерние элементы.

Перетаскиваемый элемент

const KanbanItem = ({ id, children }) => {
  const ref = useRef(null);
  const [{ isDragging }, drag] = useDrag({
    item: { type: "card", id },
    collect: monitor => ({
      isDragging: monitor.isDragging()
    })
  });
  const opacity = isDragging ? 0 : 1;
  drag(ref);
  return (
    <div ref={ref} style={{ opacity }}>
      {children}
    </div>
  );
};

Это похоже на предыдущий компонент, но мы определяем объект перетаскивания. Мы обеспечиваем уменьшение непрозрачности перетаскиваемого объекта. Опять же, здесь мы не отображаем какой-либо пользовательский интерфейс, вместо этого он передается как дочерние элементы. Фактически, это полезная стратегия, которая помогает нам повторно использовать везде, где нам нужно реализовать логику перетаскивания.

Если цикл перетаскивания завершен, состояние обновляется в соответствии с приведенной ниже логикой.

const Kanban = () => {  
  const [tasks, setTaskStatus] = useState(tasksList);
  const changeTaskStatus = useCallback(    
    (id, status) => {      
      let task = tasks.find(task => task._id === id);
      const taskIndex = tasks.indexOf(task);
      task = { ...task, status };
      let newTasks = update(tasks, {
        [taskIndex]: { $set: task }
      });
      setTaskStatus(newTasks);
    },
    [tasks]
  );
  ....
}

changeTaskStatus вызывается при успешном перетаскивании, и мы используем хук useCallback, который возвращает мемоизированную версию. обратного вызова, чтобы избежать ненужного рендеринга. Мы используем функцию update из библиотеки immutability-helper для получения новых обновленных данных.

И последнее замечание. Это нормально работает только на компьютерах, но не на сенсорных экранах, потому что перетаскивание HTML5 изначально не доступно на сенсорных устройствах. Но есть и другие альтернативы backend, которые отлично работают с react-dnd.