Как использовать API перетаскивания HTML в React
Опубликовано: 2022-03-10API перетаскивания — одна из самых крутых функций HTML. Это помогает нам реализовать функции перетаскивания в веб-браузерах.
В текущем контексте мы будем перетаскивать файлы из-за пределов браузера. При удалении файлов мы помещаем их в список и отображаем их имена. Имея файлы на руках, мы могли бы затем выполнить какую-либо другую операцию с файлами, например, загрузить их на облачный сервер.
В этом руководстве мы сосредоточимся на том, как реализовать действие перетаскивания в приложении React. Если вам нужна простая реализация JavaScript
, возможно, вы сначала хотели бы прочитать «Как сделать загрузчик файлов с помощью перетаскивания с помощью ванильного JavaScript», отличный учебник, написанный Джозефом Циммерманом не так давно.
События dragenter
, dragleave
, dragover
и drop
Существует восемь различных событий перетаскивания. Каждый из них срабатывает на разных этапах операции перетаскивания. В этом уроке мы сосредоточимся на четырех, которые запускаются, когда элемент помещается в зону перетаскивания: dragenter
, dragleave
, dragover
и drop
.
- Событие
dragenter
срабатывает, когда перетаскиваемый элемент попадает в допустимую цель перетаскивания. - Событие
dragleave
срабатывает, когда перетаскиваемый элемент покидает допустимую цель перетаскивания. - Событие
dragover
срабатывает, когда перетаскиваемый элемент перетаскивается на допустимую цель перетаскивания. (Он срабатывает каждые несколько сотен миллисекунд.) - Событие
drop
срабатывает, когда элемент падает на допустимую цель перетаскивания, т. е. перетаскивается и отпускается.
Мы можем превратить любой HTML-элемент в допустимую цель перетаскивания, определив атрибуты обработчиков событий ondragover
и ondrop
.
Вы можете узнать все о восьми событиях из веб-документов MDN.
События перетаскивания в React
Для начала клонируйте репозиторий учебника по этому URL-адресу:
https://github.com/chidimo/react-dnd.git
Проверьте ветку 01-start
. Убедитесь, что у вас также установлена yarn
. Вы можете получить его на сайте yarnpkg.com.
Но если хотите, создайте новый проект React и замените содержимое App.js кодом ниже:
import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <h1>React drag-and-drop component</h1> </div> ); } export default App;
Кроме того, замените содержимое App.css следующим стилем CSS:
.App { margin: 2rem; text-align: center; } h1 { color: #07F; } .drag-drop-zone { padding: 2rem; text-align: center; background: #07F; border-radius: 0.5rem; box-shadow: 5px 5px 10px #C0C0C0; } .drag-drop-zone p { color: #FFF; } .drag-drop-zone.inside-drag-area { opacity: 0.7; } .dropped-files li { color: #07F; padding: 3px; text-align: left; font-weight: bold; }
Если вы клонировали репозиторий, введите следующие команды (по порядку), чтобы запустить приложение:
yarn # install dependencies yarn start # start the app
Следующим шагом является создание компонента перетаскивания. Создайте файл DragAndDrop.js внутри папки src/
. Введите следующую функцию внутри файла:
import React from 'react'; const DragAndDrop = props => { const handleDragEnter = e => { e.preventDefault(); e.stopPropagation(); }; const handleDragLeave = e => { e.preventDefault(); e.stopPropagation(); }; const handleDragOver = e => { e.preventDefault(); e.stopPropagation(); }; const handleDrop = e => { e.preventDefault(); e.stopPropagation(); }; return ( <div className={'drag-drop-zone'} onDrop={e => handleDrop(e)} onDragOver={e => handleDragOver(e)} onDragEnter={e => handleDragEnter(e)} onDragLeave={e => handleDragLeave(e)} > <p>Drag files here to upload</p> </div> ); }; export default DragAndDrop;
В возвращаемом div
мы определили наши атрибуты обработчика HTML
-событий фокуса. Вы можете видеть, что единственное отличие от чистого HTML
— это Camel-Case.
Теперь div
является допустимой целью перетаскивания, так как мы определили атрибуты обработчиков событий onDragOver
и onDrop
.
Мы также определили функции для обработки этих событий. Каждая из этих функций-обработчиков получает объект события в качестве аргумента.
Для каждого из обработчиков событий мы вызываем preventDefault()
, чтобы запретить браузеру выполнять поведение по умолчанию. Браузер по умолчанию открывает перетащенный файл. Мы также вызываем stopPropagation()
, чтобы убедиться, что событие не распространяется от дочерних элементов к родительским.
Импортируйте компонент DragAndDrop
в компонент App
и отобразите его под заголовком.
<div className="App"> <h1>React drag-and-drop component</h1> <DragAndDrop /> </div>
Теперь просмотрите компонент в браузере, и вы должны увидеть что-то вроде изображения ниже.

div
для преобразования в зону перетаскивания (большой предварительный просмотр) Если вы следуете репо, соответствующая ветка — 02-start-dragndrop
Управление состоянием с помощью useReducer
Нашим следующим шагом будет написание логики для каждого из наших обработчиков событий. Прежде чем мы это сделаем, мы должны подумать, как мы собираемся отслеживать удаленные файлы. Здесь мы начинаем думать об управлении состоянием.
Мы будем отслеживать следующие состояния во время операции перетаскивания:
-
dropDepth
Это будет целое число. Мы будем использовать его, чтобы отслеживать, на сколько уровней мы находимся в зоне сброса. Позже я объясню это иллюстрацией. ( Спасибо Егору Егорову за то, что он пролил на меня свет! ) -
inDropZone
Это будет логическое значение. Мы будем использовать это, чтобы отслеживать, находимся ли мы внутри зоны сброса или нет. -
FileList
Это будет список. Мы будем использовать его для отслеживания файлов, которые были перемещены в зону сброса.
Для обработки состояний React предоставляет useState
и useReducer
. Мы выберем хук useReducer
, учитывая, что мы будем иметь дело с ситуациями, когда состояние зависит от предыдущего состояния.
useReducer
принимает редьюсер типа (state, action) => newState
и возвращает текущее состояние в паре с методом dispatch
.
Вы можете узнать больше о useReducer
в документации React.
Внутри компонента App
(перед оператором return
) добавьте следующий код:
... const reducer = (state, action) => { switch (action.type) { case 'SET_DROP_DEPTH': return { ...state, dropDepth: action.dropDepth } case 'SET_IN_DROP_ZONE': return { ...state, inDropZone: action.inDropZone }; case 'ADD_FILE_TO_LIST': return { ...state, fileList: state.fileList.concat(action.files) }; default: return state; } }; const [data, dispatch] = React.useReducer( reducer, { dropDepth: 0, inDropZone: false, fileList: [] } ) ...
useReducer
принимает два аргумента: редьюсер и начальное состояние. Он возвращает текущее состояние и функцию dispatch
, с помощью которой можно обновить состояние. Состояние обновляется путем отправки действия, которое содержит type
и необязательную полезную нагрузку. Обновление состояния компонента зависит от того, что возвращается оператором case в результате типа действия. (Обратите внимание, что наше начальное состояние — это object
.)

Для каждой из переменных состояния мы определили соответствующий оператор case для ее обновления. Обновление выполняется путем вызова функции dispatch
, возвращаемой useReducer
.
Теперь передайте data
и DragAndDrop
их в качестве props
компоненту dispatch
, который у вас есть в файле App.js :
<DragAndDrop data={data} dispatch={dispatch} />
В верхней части компонента DragAndDrop
мы можем получить доступ к обоим значениям из props
.
const { data, dispatch } = props;
Если вы следите за репозиторием, соответствующая ветка — 03-define-reducers
.
Давайте закончим логику наших обработчиков событий. Обратите внимание, что многоточие представляет собой две строки:
e.preventDefault() e.stopPropagation() const handleDragEnter = e => { ... dispatch({ type: 'SET_DROP_DEPTH', dropDepth: data.dropDepth + 1 }); }; const handleDragLeave = e => { ... dispatch({ type: 'SET_DROP_DEPTH', dropDepth: data.dropDepth - 1 }); if (data.dropDepth > 0) return dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: false }) };
На приведенном ниже рисунке у нас есть вложенные зоны перетаскивания A и B. A — это наша интересующая нас зона. Здесь мы хотим прослушивать события перетаскивания.

ondragenter
и ondragleave
(большой предварительный просмотр) При перетаскивании в зону перетаскивания каждый раз, когда мы сталкиваемся с границей, запускается событие ondragenter
. Это происходит на границах A-in
и B-in
. Так как мы входим в зону, мы увеличиваем dropDepth
.
Аналогично, при перетаскивании из зоны перетаскивания каждый раз, когда мы сталкиваемся с границей, запускается событие ondragleave
. Это происходит на границах A-out
и B-out
. Поскольку мы покидаем зону, мы уменьшаем значение dropDepth
. Обратите внимание, что мы не устанавливаем для inDropZone
значение false
на границе B-out
. Именно поэтому у нас есть эта строка для проверки dropDepth и возврата из функции dropDepth
больше 0
.
if (data.dropDepth > 0) return
Это связано с тем, что даже несмотря на то, что событие ondragleave
запускается, мы все еще находимся в зоне A. Только после того, как мы нажмем A-out
, а dropDepth
теперь равен 0
, мы устанавливаем для inDropZone
значение false
. На данный момент мы покинули все зоны сброса.
const handleDragOver = e => { ... e.dataTransfer.dropEffect = 'copy'; dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: true }); };
Каждый раз, когда срабатывает это событие, мы устанавливаем для inDropZone
значение true
. Это говорит нам о том, что мы находимся внутри зоны сброса. Мы также устанавливаем dropEffect
объекта dataTransfer
на copy
. На Mac это приводит к отображению зеленого знака плюса при перетаскивании элемента в зоне перетаскивания.
const handleDrop = e => { ... let files = [...e.dataTransfer.files]; if (files && files.length > 0) { const existingFiles = data.fileList.map(f => f.name) files = files.filter(f => !existingFiles.includes(f.name)) dispatch({ type: 'ADD_FILE_TO_LIST', files }); e.dataTransfer.clearData(); dispatch({ type: 'SET_DROP_DEPTH', dropDepth: 0 }); dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: false }); } };
Мы можем получить доступ к удаленным файлам с помощью e.dataTransfer.files
. Значение представляет собой объект, подобный массиву, поэтому мы используем синтаксис расширения массива, чтобы преобразовать его в массив JavaScript
.
Теперь нам нужно проверить, есть ли хотя бы один файл, прежде чем пытаться добавить его в наш массив файлов. Мы также следим за тем, чтобы не включать файлы, которые уже есть в нашем fileList
. Объект dataTransfer
очищается при подготовке к следующей операции перетаскивания. Мы также сбрасываем значения dropDepth
и inDropZone
.
Обновите className
div
в компоненте DragAndDrop
. Это условно изменит className
div
в зависимости от значения data.inDropZone
.
<div className={data.inDropZone ? 'drag-drop-zone inside-drag-area' : 'drag-drop-zone'} ... > <p>Drag files here to upload</p> </div>
Визуализируйте список файлов в App.js путем сопоставления через data.fileList
.
<div className="App"> <h1>React drag-and-drop component</h1> <DragAndDrop data={data} dispatch={dispatch} /> <ol className="dropped-files"> {data.fileList.map(f => { return ( <li key={f.name}>{f.name}</li> ) })} </ol> </div>
Теперь попробуйте перетащить несколько файлов в зону перетаскивания. Вы увидите, что когда мы входим в зону перетаскивания, фон становится менее непрозрачным, поскольку активируется класс inside-drag-area
.
Когда вы освободите файлы внутри зоны сброса, вы увидите имена файлов, перечисленные под зоной сброса:


Полная версия этого руководства находится в ветке 04-finish-handlers
.
Заключение
Мы увидели, как обрабатывать загрузку файлов в React с помощью API перетаскивания HTML
. Мы также узнали, как управлять состоянием с помощью хука useReducer
. Мы могли бы расширить функцию file handleDrop
. Например, мы могли бы добавить еще одну проверку для ограничения размеров файлов, если бы захотели. Это может произойти до или после проверки существующих файлов. Мы также могли бы сделать зону перетаскивания кликабельной, не затрагивая функциональность перетаскивания.
Ресурсы
- «Справочник по API хуков:
useReducer
», React Docs - «HTML Drag-and-Drop API», веб-документы MDN
- «Примеры веб-разработки и XML-разработки с использованием DOM», веб-документы MDN
- «Как сделать загрузчик файлов с помощью перетаскивания с помощью ванильного JavaScript», Джозеф Циммерман, Smashing Magazine
- «Простая загрузка файлов перетаскиванием в React», Егор Егоров, Medium