Создание приложения drag and drop с React без библиотек 👆!

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

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

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

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

 

Оглавление.

📌 Технологии для использования.

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

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

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

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

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

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

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

📌 Показываю карты.

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

📌 Выполнение функции Drop.

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

📌 Выполнение функций по загрузке контейнеров.

📌 Необязательно. Рефакторинг кода в DragAndDrop.tsx.

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

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

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

 

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

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

 

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

Мы назовем проект: 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 и внутри add:

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, этому prop мы передаем в качестве значения данные, которые мы создали в папке 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, которые мы определили ранее.

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

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 мы изменяем интерфейс Props, добавляя свойство 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>
    )
}
Войдите в полноэкранный режим Выход из полноэкранного режима

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

Это происходит потому, что мы визуализируем ContainerCards 3 раза.

Но подождите, единственное свойство, которое сделает разницу между этими 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, так как в начале работы приложения оно не будет перетаскиваться.
    • Это будет верно только при перетаскивании карты.
  • Функция, которая получает булево значение, поможет нам изменить значение на состояние, это делается для того, чтобы избежать передачи сеттера 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.

Эта функция только установит значение состояния isDragging в 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: это информация, которую мы хотим сохранить при перетаскивании элемента. Он принимает только строку. В этом случае мы будем хранить идентификатор карты.

ПРИМЕЧАНИЕ: внутри 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>
    )
}
Войдите в полноэкранный режим Выход из полноэкранного режима

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

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

 

👉 Выполнение функции Drop.

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

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

Сначала установим список героев в состояние и сможем обновить его, когда карта будет сброшена в другой контейнер, в это время мы обновим свойство 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: идентификатор карты, он будет иметь тип номер.
  • 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, которую компонент передает нам по props, (вы должны отстроить ее от компонента).

    • Сначала мы передаем ему id, который мы получили из свойства getData в dataTransfer, уже преобразованный в число.
    • Затем мы передаем статус, который мы получили с помощью реквизита, в компонент.
  • Наконец, мы вызываем 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

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. 🙌

 

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

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
  • Ванильный 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
Добавить комментарий