Создание приложения с помощью Drag and Drop с React без библиотек 👆!

Приложения с перетаскиванием очень распространены в наши дни, они отлично подходят для пользовательского опыта внутри приложения. **И вы наверняка хотели бы реализовать их в своем следующем проекте.

На этот раз я покажу вам, как сделать приложение с функцией drag&drop, но без использования каких-либо внешних библиотек, только с помощью React JS.

🚨 Примечание: Этот пост требует от вас знания основ React с TypeScript (базовые хуки и пользовательские хуки).

Любой вид обратной связи приветствуется, спасибо и надеюсь, что статья вам понравится.🤗

 

Оглавление.

📌 Используемые технологии.

📌 Создание проекта.

📌 Первые шаги.

📌 Создание наших карточек.

📌 Создание контейнеров для наших карточек.

📌 Определение типа и интерфейса для информации о карточках.

📌 Создание компонента DragAndDrop.tsx.

📌 Добавление некоторых данных для создания карточек.

📌 Показ некоторых карточек.

📌 Выполнение перетаскивания.

📌 Выполнение операции Drop.

📌 Создание состояния для хранения карточек.

📌 Выполнение функций, чтобы сделать сброс в контейнеры.

📌 Дополнительно. Рефакторинг кода в DragAndDrop.tsx.

📌 Заключение.

📌 Живая демонстрация.

📌 Исходный код.

 

👉 Используемые технологии.

  • ▶️ React JS (v.18)
  • ▶️ Vite JS
  • ▶️ TypeScript
  • ▶️ CSS vanilla (стили можно найти в репозитории в конце этого поста)

 

👉 Создание проекта.

Мы назовем проект: dnd-app (опционально, вы можете назвать его как угодно).

npm init vite@latest
Вход в полноэкранный режим Выход из полноэкранного режима

Мы создаем проект с помощью Vite JS и выбираем React with TypeScript.

Затем мы выполняем следующую команду для перехода в только что созданную директорию.

cd dnd-app
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем мы устанавливаем зависимости.

npm install
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем открываем проект в редакторе кода (в моем случае VS code).

code .
Входим в полноэкранный режим Выходим из полноэкранного режима

Затем с помощью этой команды мы поднимем сервер разработки и, наконец, зайдем в браузер и откроем http://localhost:5173 (в vite версии 2 порт был localhost:3000, а в новой версии порт localhost:5173).

npm run dev
Вход в полноэкранный режим Выйти из полноэкранного режима

 

👉 Первые шаги.

Сразу создаем папку src/components и добавляем в нее файл Title.tsx и внутри добавляем:

export const Title = () => {
    return (
        <div className="title flex">
            <h1>Creating basic Drag & Drop 👆 </h1>
            <span>( without external libraries )</span>
        </div>
    )
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь, внутри файла src/App.tsx мы удаляем все содержимое файла и размещаем функциональный компонент, который показывает заголовок, который мы только что создали.

import { Title } from "./components/Title"

const App = () => {
  return (
    <div className="container-main flex">
        <Title />
    </div>
  )
}
export default App
Вход в полноэкранный режим Выйти из полноэкранного режима

Это должно выглядеть следующим образом 👀:

 

👉 Создаем наши карточки.

В папку src/components добавляем файл CardItem.tsx.

В данный момент он не будет получать никаких реквизитов, он сделает это позже.


export const CardItem = () => {
    return (
        <div className='card-container'>
            <p>content</p>
        </div>
    )
}
Вход в полноэкранный режим Выход из полноэкранного режима

Мы пока НЕ будем использовать компонент Card в файле, но если вы хотите, вы можете импортировать его в файл src/App.tsx, чтобы вы могли стилизовать его и увидеть на экране.

 

👉 Создание контейнеров для наших карточек.

Теперь давайте создадим наш контейнер для карточек.
Внутри папки src/components добавим файл ContainerCards.tsx и добавим следующее:

Этот компонент на данный момент получает в качестве параметра статус (вы можете видеть, какой тип является статусом).

import { Status } from '../interfaces'

interface Props {
  status: Status
}

export const ContainerCards = ({ status }: Props) => {

    return (
        <div className="layout-cards" >
            <p>{status} hero</p>
            {/* Cards */}
        </div>
    )
}

Вход в полноэкранный режим Выход из полноэкранного режима

 

🟠 Определение типа и интерфейса для информации о карточках.

Тип Статус выглядит следующим образом:

export type Status = 'good' | 'bad' | 'normal'
Войти в полноэкранный режим Выйти из полноэкранного режима

Этот тип находится в папке src/interfaces внутри файла index.ts (который необходимо создать, так как тип Status будет использоваться в нескольких файлах).

При создании index.ts в src/interfaces также добавьте следующий интерфейс.

Вот как будут выглядеть данные карточки.

export interface Data {
    id: number
    content: string
    status: Status
}
Вход в полноэкранный режим Выход из полноэкранного режима

 

👉 Создание компонента DragAndDrop.tsx.

Итак, на данный момент мы уже создали компонент, который будет содержать карты, но нам нужно 3 контейнера для карт:

  • Один для хороших героев.
  • Один для нормальных героев.
  • Один для плохих героев.

В папку src/components добавляем файл DragAndDrop.tsx и добавляем следующее:

import { Status } from "../interfaces"
import { ContainerCards } from "./ContainerCards"

const typesHero: Status[] = ['good', 'normal', 'bad']

export const DragAndDrop = () => {
    return (
        <div className="grid">
            {
                typesHero.map( container => (
                    <ContainerCards
                        status={container}
                        key={container}
                    />
                ))
            }
        </div>
    )
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Этот компонент должен быть добавлен в src/App.tsx.

import { DragAndDrop} from "./components/DragAndDrop"
import { Title } from "./components/Title"

const App = () => {

  return (
    <div className="container-main flex">
      <Title />
      <DragAndDrop />
    </div>
  )
}
export default App
Вход в полноэкранный режим Выход из полноэкранного режима

На данный момент это должно выглядеть примерно так 👀….

Теперь у нас есть контейнеры, в которые можно бросать и сортировать карты. 👋

Теперь нам нужно создать несколько карточек.

 

👉 Добавление некоторых данных для создания карточек.

Теперь создадим папку src/assets и внутри нее файл index.ts, который будет содержать список с данными для заполнения карточек.

import { Data } from "../interfaces";

export const data: Data[] = [
    {
        id: 1,
        content: 'Aqua-man',
        status: 'good'
    },
    {
        id: 2,
        content: 'Flash',
        status: 'normal'
    },
    {
        id: 3,
        content: 'Green Lantern',
        status: 'good'
    },
    {
        id: 4,
        content: 'Batman',
        status: 'bad'
    },
]

Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, вернувшись в src/components/DragAndDrop.tsx в компонент ContainerCards, передадим новый prop под названием items, которому в качестве значения передадим данные, созданные нами в папке src/assets.

import { ContainerCards } from "./ContainerCards"
import { Status } from "../interfaces"
import { data } from "../assets"

const typesHero: Status[] = ['good', 'normal', 'bad']

export const DragAndDrop = () => {
    return (
        <div className="grid">
            {
                typesHero.map( container => (
                    <ContainerCards
                        status={container}
                        key={container}
                        items={data}
                    />
                ))
            }
        </div>
    )
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Это приведет к ошибке, потому что items не является свойством, которое ожидает ContainerCards. 😥

Но мы исправим это в следующем разделе. 👇

 

👉 Показ некоторых карточек.

Чтобы отобразить некоторые карточки, нам нужно внести некоторые изменения в параметры каждого компонента.

1 — Сначала компонент src/components/CardItem.tsx.

Он будет получать в качестве реквизитов данные типа Data, которые мы определили ранее.

Сразу же покажем содержимое свойства внутри данных.

import { Data } from "../interfaces"

interface Props {
    data: Data
}

export const CardItem = ({ data, handleDragging }: Props) => {

    return (
        <div className='card-container'>
            <p>{data.content}</p>
        </div>
    )
}
Вход в полноэкранный режим Выход из полноэкранного режима

2 — В компоненте src/components/ContainerCards.tsx мы изменим интерфейс реквизита, добавив свойство items, которое является списком Data, и деструктурируем его в компоненте.

import { Data, Status } from "../interfaces"

interface Props {
    items: Data[]
    status: Status
}

export const ContainerCards = ({ items = [], status }: Props) => {

    return (
        <div className="layout-cards">
            <p>{status} hero</p>
        </div>
    )
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Затем в теге p мы выполняем итерацию по элементам.
И возвращаем CardItem.tsx, отправляя item в свойство data CardItem.

import { Data, Status } from "../interfaces"
import { CardItem } from "./CardItem"

interface Props {
    items: Data[]
    status: Status
}

export const ContainerCards = ({ items = [], status}: Props) => {

    return (
        <div className="layout-cards">
            <p>{status} hero</p>
            {
                items.map(item => (
                    <CardItem
                        data={item}
                        key={item.id}
                    />
                ))
            }
        </div>
    )
}
Вход в полноэкранный режим Выход из полноэкранного режима

При этом появится предупреждение о том, что клавиши повторяются 😥.

Это потому, что мы рендерим 3 раза ContainerCards.

Но подождите, единственное свойство, которое будет делать разницу между этими 3 компонентами — это статус.

Поэтому мы сделаем следующее условие:

  • Если статус, полученный компонентом ContainerCards, равен статусу элемента (т.е. супергероя), то отрисовываем его, иначе возвращаем false.
import { Data, Status } from "../interfaces"
import { CardItem } from "./CardItem"

interface Props {
    items: Data[]
    status: Status
}

export const ContainerCards = ({ items = [], status }: Props) => {

    return (
        <div className="layout-cards">
            <p>{status} hero</p>
            {
                items.map(item => (
                    status === item.status
                    && <CardItem
                        data={item}
                        key={item.id}
                    />
                ))
            }
        </div>
    )
}
Вход в полноэкранный режим Выход из полноэкранного режима

Таким образом, мы избежим конфликта с ключами и карточки будут отсортированы следующим образом 👀….

 

👉 Выполнение перетаскивания.

Чтобы выполнить функцию перетаскивания, мы сначала определим состояние и функцию в src/components/DragAndDrop.tsx.

  • Состояние поможет нам узнать, выполняется ли перетаскивание, и таким образом изменить стили.

    • По умолчанию оно будет равно false, так как в начале работы приложения оно не будет выполнять перетаскивание.
    • Оно будет истинным только при перетаскивании карты.
  • Функция, которая получает булево значение, поможет нам изменить значение на состояние, это делается для того, чтобы избежать передачи сеттера setter setIsDragging как prop.

Мы передаем в качестве prop компоненту ContainerCards:

  • isDragging, он будет иметь значение состояния.
  • handleDragging, это будет функция, которую мы создадим для обновления состояния.
import { ContainerCards } from "./ContainerCards"
import { data } from "../assets"
import { Status } from "../interfaces"

const typesHero: Status[] = ['good', 'normal', 'bad']

export const DragAndDrop = () => {

  const [isDragging, setIsDragging] = useState(false)

  const handleDragging = (dragging: boolean) => setIsDragging(dragging)


    return (
        <div className="grid">
            {
                typesHero.map(container => (
                    <ContainerCards
                        items={data}
                        status={container}
                        key={container}

                        isDragging={isDragging}
                        handleDragging={handleDragging}
                    />
                ))
            }
        </div>
    )
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Это не удастся, потому что ContainerCards не ожидает этих свойств.

Поэтому нам придется изменить интерфейс ContainerCards.
Файл src/components/ContainerCards.tsx.


interface Props {
    items: Data[]
    status: Status
    isDragging: boolean
    handleDragging: (dragging: boolean) => void
}
Вход в полноэкранный режим Выход из полноэкранного режима

И одним движением мы получаем эти реквизиты.

  • Обратите внимание, что в className div мы помещаем условие, где если isDragging равен true, то мы добавляем класс layout-dragging. Этот класс будет изменять цвет фона и границы контейнера только при перетаскивании карточки.

  • Обратите внимание, что мы также передаем CardItem новое свойство handleDragging, потому что карточка является компонентом, который будет обновлять состояние, созданное нами ранее.

import { CardItem } from "./CardItem"
import { Data, Status } from "../interfaces"

interface Props {
    items: Data[]
    status: Status
    isDragging: boolean
    handleDragging: (dragging: boolean) => void
}

export const ContainerCards = ({ items = [], status, isDragging, handleDragging }: Props) => {

    return (
        <div
            className={`layout-cards ${isDragging ? 'layout-dragging' : ''}`}
        >
            <p>{status} hero</p>
            {
                items.map(item => (
                    status === item.status
                    && <CardItem
                        data={item}
                        key={item.id}
                        handleDragging={handleDragging}
                    />
                ))
            }
        </div>
    )
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

Теперь в файле src/components/CardItem.tsx мы изменим интерфейс

interface Props {
    data: Data,
    handleDragging: (dragging: boolean) => void
}
Вход в полноэкранный режим Выйти из полноэкранного режима

И теперь да, мы начинаем добавлять функциональность перетаскивания в этот компонент.
Сначала в div, который представляет собой всю карточку, мы добавляем атрибут draggable, чтобы указать, что этот компонент можно перетаскивать.

import { Data } from "../interfaces"

interface Props {
    data: Data,
    handleDragging: (dragging: boolean) => void
}

export const CardItem = ({ data, handleDragging }: Props) => {
    return (
        <div
            className='card-container'
            draggable
        >
            <p>{data.content}</p>
        </div>
    )
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Затем мы добавляем атрибут onDragEnd, который будет выполнять функцию handleDragEnd.

Эта функция только установит значение статуса isDraging в false, потому что при выполнении onDragEnd карта больше не будет перетаскиваться, поэтому мы должны удалить стили при перетаскивании, то есть вернуть все стили, как в начале.

import { Data } from "../interfaces"

interface Props {
    data: Data,
    handleDragging: (dragging: boolean) => void
}

export const CardItem = ({ data, handleDragging }: Props) => {


    const handleDragEnd = () => handleDragging(false)

    return (
        <div
            className='card-container'
            draggable
            onDragEnd={handleDragEnd}
        >
            <p>{data.content}</p>
        </div>
    )
}
Вход в полноэкранный режим Выход из полноэкранного режима

Затем добавляем атрибут onDragStart (он выполняется, когда компонент начинает перетаскиваться, если бы мы не добавили атрибут draggable, то onDragStart не выполнялся бы).

onDragStart выполнит функцию handleDragStart.

Эта функция получает событие, и внутри события есть свойство, которое нас интересует, это dataTransfer.

Свойство dataTransfer позволяет нам содержать или получать данные при перетаскивании элемента.

Свойство setData в свойстве dataTransfer устанавливает данные, которые мы хотим сохранить при перетаскивании элемента, и принимает два параметра:

  • format: формат сохраняемых данных, который является «text».

  • data: информация, которую мы хотим сохранить при перетаскивании элемента. Он принимает только строку. В данном случае мы будем хранить id карточки.

ПРИМЕЧАНИЕ: внутри dataTransfer также есть свойство clearData, которое очищает кэш данных, которые мы храним. В данном случае выполнять его не нужно, так как мы будем перезаписывать тот же идентификатор ‘text’.

После сохранения данных мы выполняем handleDragging, передавая значение true, чтобы указать пользователю, что мы перетаскиваем элемент.

import { Data } from "../interfaces"

interface Props {
    data: Data,
    handleDragging: (dragging: boolean) => void
}

export const CardItem = ({ data, handleDragging }: Props) => {

    const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
        e.dataTransfer.setData('text', `${data.id}`)
        handleDragging(true)
    }
    const handleDragEnd = () => handleDragging(false)

    return (
        <div
            className='card-container'
            draggable
            onDragStart={handleDragStart}
            onDragEnd={handleDragEnd}
        >
            <p>{data.content}</p>
        </div>
    )
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

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

 

👉 Выполнение сброса.

Прежде чем мы выполним часть освобождения элемента, мы должны сначала сделать другие вещи.

🟠 Создание состояния для хранения карт.

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

Для этого перейдем в src/components/DragAndDrop.tsx и создадим новый статус.
Его начальным значением будут данные, которые мы ранее определили в src/assets.

import { data } from "../assets"

const [listItems, setListItems] = useState<Data[]>(data)
Вход в полноэкранный режим Выход из полноэкранного режима

И теперь при рендеринге компонента ContainerCards вместо того, чтобы передавать значение data реквизиту items, мы будем передавать ему значение состояния listItems.

import { ContainerCards } from "./ContainerCards"
import { data } from "../assets"
import { Status } from "../interfaces"

const typesHero: Status[] = ['good', 'normal', 'bad']

export const DragAndDrop = () => {

  const [isDragging, setIsDragging] = useState(false)
  const [listItems, setListItems] = useState<Data[]>(data)

  const handleDragging = (dragging: boolean) => setIsDragging(dragging)


    return (
        <div className="grid">
            {
                typesHero.map(container => (
                    <ContainerCards
                        items={listItems}
                        status={container}
                        key={container}

                        isDragging={isDragging}
                        handleDragging={handleDragging}
                    />
                ))
            }
        </div>
    )
}
Вход в полноэкранный режим Выход из полноэкранного режима

Затем мы создадим функцию для обновления состояния listItems.
Мы назовем ее handleUpdateList, и она будет принимать два параметра:

  • id: идентификатор карточки, он будет иметь тип number.
  • status: новый статус карточки, он будет типа Status.

Внутри функции …

1 — Сначала мы будем искать элемент в значении статуса listItems, по идентификатору.

2 — Мы оценим, существуют ли эти данные, и если переданный нам статус отличается от того, который он уже имеет, то мы внесем изменения в статус.

3 — В рамках условия мы обращаемся к найденной карточке и обновляем ее свойство status, присваивая ей новый статус, который передается нам параметром в функции.

4 — Вызываем setListItems для обновления статуса, размещаем:

  • Карточку с ее обновленным свойством статуса.

  • Новый массив, фильтруя элементы, чтобы удалить обновляемую карточку и избежать дублирования информации.

Теперь в компонент ContainerCards добавим новое свойство handleUpdateList и отправим ему только что созданную функцию handleUpdateList.

import { ContainerCards } from "./ContainerCards"
import { data } from "../assets"
import { Status } from "../interfaces"

const typesHero: Status[] = ['good', 'normal', 'bad']

export const DragAndDrop = () => {

  const [isDragging, setIsDragging] = useState(false)
  const [listItems, setListItems] = useState<Data[]>(data)

  const handleDragging = (dragging: boolean) => setIsDragging(dragging)

  const handleUpdateList = (id: number, status: Status) => {

       let card = listItems.find(item => item.id === id)

       if (card && card.status !== status) {

           card.status = status

           setListItems( prev => ([
                card!,
                ...prev.filter(item => item.id !== id)
            ]))
       }
   }

    return (
        <div className="grid">
            {
                typesHero.map(container => (
                    <ContainerCards
                        items={listItems}
                        status={container}
                        key={container}

                        isDragging={isDragging}
                        handleDragging={handleDragging}
                        handleUpdateList={handleUpdateList}
                    />
                ))
            }
        </div>
    )
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Это даст нам ошибку, потому что компонент ContainerCards не ожидает свойства handleUpdateList, поэтому мы должны обновить интерфейс ContainerCards.

В src/components/ContainerCards.tsx:

interface Props {
    items: Data[]
    status: Status
    isDragging: boolean
    handleDragging: (dragging: boolean) => void
    handleUpdateList: (id: number, status: Status) => void
}
Вход в полноэкранный режим Выход из полноэкранного режима

 

👉 Выполняем функции, чтобы сделать падение в контейнерах.

Мы находимся в src/components/ContainerCards.tsx.

Внутри компонента мы собираемся установить два новых свойства для элемента div.

  • onDragOver*: происходит, когда перетаскиваемый элемент перетаскивается через допустимую цель падения. Мы передаем ему функцию **handleDragOver*, которую создадим через некоторое время.

  • onDrop*: происходит, когда перетаскиваемый элемент опускается. Мы передаем ему функцию **handleDrop*, которую мы создадим в ближайшее время.

<div
    className={`layout-cards ${isDragging ? 'layout-dragging' : ''}`}
    onDragOver={handleDragOver}
    onDrop={handleDrop}
>
    <p>{status} hero</p>
    {
        items.map(item => (
            status === item.status
            && <CardItem
                data={item}
                key={item.id}
                handleDragging={handleDragging}
            />
        ))
    }
</div>
Вход в полноэкранный режим Выход из полноэкранного режима

Функция handleDragOver будет делать только это.
Во-первых, она получит событие, которое испускает onDragOver.

Поскольку по умолчанию данные не могут быть сброшены на другие элементы, и чтобы разрешить сброс, мы должны избежать поведения по умолчанию.

const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault()
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь функция handleDrop

  • Во-первых, вы получите событие, которое испускает функция onDrop.

  • Внутри функции мы избегаем поведения по умолчанию, которое более заметно с изображениями (когда мы бросаем изображение в каком-либо месте нашего приложения, оно открывается, выводя нас из приложения).

  • Затем из события мы получаем свойство dataTransfer и через свойство getData свойства dataTransfer выполняем его, отправляя идентификатор, из которого мы получим ID карты.

    • Знак + в начале e.dataTransfer.getData('text') служит для преобразования значения в число.
  • Затем мы вызовем функцию handleUpdateList, которую компонент передает нам по реквизитам (мы должны разгруппировать ее от компонента).

    • Сначала мы передадим ей id, который мы получили из свойства getData в dataTransfer, уже преобразованный в число.
    • Затем мы передаем ему статус, который мы получили из props в компоненте.
  • Наконец, мы вызываем handleDragging, передавая значение false, чтобы указать пользователю, что мы больше ничего не перетаскиваем.

const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault()
    const id = +e.dataTransfer.getData('text')
    handleUpdateList(id, status)
    handleDragging(false)
}
Вход в полноэкранный режим Выход из полноэкранного режима

Вот как будет выглядеть код в src/components/ContainerCards.tsx:

import { Data, Status } from "../interfaces"
import { CardItem } from "./CardItem"

interface Props {
    items: Data[]
    status: Status
    isDragging: boolean
    handleUpdateList: (id: number, status: Status) => void
    handleDragging: (dragging: boolean) => void
}

export const ContainerCards = ({ items = [], status, isDragging, handleDragging, handleUpdateList }: Props) => {

    const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
        e.preventDefault()
        handleUpdateList(+e.dataTransfer.getData('text'), status)
        handleDragging(false)
    }

    const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => e.preventDefault()

    return (
        <div
            className={`layout-cards ${isDragging ? 'layout-dragging' : ''}`}
            onDrop={handleDrop}
            onDragOver={handleDragOver}
        >
            <p>{status} hero</p>
            {
                items.map(item => (
                    status === item.status
                    && <CardItem
                        data={item}
                        key={item.id}
                        handleDragging={handleDragging}
                    />
                ))
            }
        </div>
    )
}
Вход в полноэкранный режим Выход из полноэкранного режима

Конечный результат должен выглядеть следующим образом 🥳!

 

👉 Дополнительно. Рефакторинг кода в DragAndDrop.tsx

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

Создадим папку src/hooks и внутри нее файл под названием useDragAndDrop.ts.

Сначала мы определим функцию, которая будет получать начальное состояние, имеющее тип Data array.

export const useDragAndDrop = (initialState: Data[]) => {}
Вход в полноэкранный режим Выход из полноэкранного режима

Из компонента DragAndDrop.tsx мы вырезаем всю логику и помещаем ее в пользовательский хук.

Начальным значением состояния listItems будет то, которое передано нам параметром хука.

И, наконец, мы возвращаем как объект:

  • isDragging.
  • listItems.
  • handleUpdateList.
  • handleDragging.
import { useState } from "react"
import { Data, Status } from "../interfaces"

export const useDragAndDrop = (initialState: Data[]) => {

    const [isDragging, setIsDragging] = useState(false)
    const [listItems, setListItems] = useState<Data[]>(initialState)

    const handleUpdateList = (id: number, status: Status) => {

       let card = listItems.find(item => item.id === id)

       if (card && card.status !== status) {

           card.status = status

           setListItems( prev => ([
                card!,
                ...prev.filter(item => item.id !== id)
            ]))
       }
   }

    const handleDragging = (dragging: boolean) => setIsDragging(dragging)

    return {
        isDragging,
        listItems,
        handleUpdateList,
        handleDragging,
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь в компоненте src/components/DragAndDrop.tsx мы вызываем наш пользовательский хук.

Мы отправляем данные в наш хук с помощью параметра и просто разгруппировываем свойства и все.

import { ContainerCards } from "./ContainerCards"
import { useDragAndDrop } from "../hooks/useDragAndDrop"
import { Status } from "../interfaces"
import { data } from "../assets"

const typesHero: Status[] = ['good', 'normal', 'bad']

export const DragAndDrop = () => {

    const { isDragging, listItems, handleDragging, handleUpdateList } = useDragAndDrop(data)

    return (
        <div className="grid">
            {
                typesHero.map(container => (
                    <ContainerCards
                        items={listItems}
                        status={container}
                        key={container}

                        isDragging={isDragging}
                        handleDragging={handleDragging}
                        handleUpdateList={handleUpdateList}
                    />
                ))
            }
        </div>
    )
}
Вход в полноэкранный режим Выход из полноэкранного режима

Это сделает ваш компонент более читабельным. 🎉

 

👉 Заключение.

Этот процесс является одним из способов создания приложения с функцией Drag & Drop без использования внешних библиотек.

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

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

Надеюсь, я помог вам понять, как выполнить это упражнение, спасибо большое, что дошли до конца! 🤗❤️

Я приглашаю вас прокомментировать, если вы считаете эту статью полезной или интересной, или если вы знаете какой-либо другой или лучший способ, как сделать drag&drop. 🙌

 

🟠 Live demo.

https://drag-and-drop-react-app.netlify.app

🟠 Исходный код.

Franklin361 / drag-and-drop-react

Создание приложения с помощью Drag & Drop с React JS 🤏

Создание приложения с помощью Drag and Drop с React без библиотек 👆!

На этот раз мы собираемся реализовать функциональность для выполнения Drag & Drop с помощью React JS и без каких-либо других внешних пакетов или библиотек!

 

 

  1. Перетаскивание карточек.
  2. Сбрасывание карточек в контейнер.
  3. Сортировка карточек.

 

  • React JS
  • TypeScript
  • Vite JS
  • Vanilla CSS 3

 

  1. Клонируйте репозиторий (у вас должен быть установлен Git).
    git clone https://github.com/Franklin361/drag-and-drop-react
Войдите в полноэкранный режим Выйдите из полноэкранного режима
  1. Установите зависимости проекта.
    npm install
Войдите в полноэкранный режим Выйдите из полноэкранного режима
  1. Запустите проект.
    npm run dev
Войти в полноэкранный режим Выйти из полноэкранного режима

 

🇲🇽 🔗 https://dev.to/franklin030601/creando-un-app-que-usa-drag-and-drop-con-react-sin-librerias—gm3🇺🇲 🔗 https://dev.to/franklin030601/creating-an-app-using-drag-and-drop-with-react-without-libraries—5cg9

Посмотреть на GitHub

Оцените статью
devanswers.ru
Добавить комментарий