So verwenden Sie die HTML-Drag-and-Drop-API in React
Veröffentlicht: 2022-03-10Die Drag-and-Drop-API ist eine der coolsten Funktionen von HTML. Es hilft uns, Drag-and-Drop-Funktionen in Webbrowsern zu implementieren.
Im aktuellen Kontext ziehen wir Dateien von außerhalb des Browsers. Beim Ablegen der Datei(en) setzen wir sie auf eine Liste und zeigen ihre Namen an. Mit den vorliegenden Dateien könnten wir dann eine andere Operation an der/den Datei(en) durchführen, zB sie auf einen Cloud-Server hochladen.
In diesem Tutorial konzentrieren wir uns darauf, wie Sie die Drag-and-Drop-Aktion in einer React-Anwendung implementieren. Wenn Sie eine einfache JavaScript
-Implementierung benötigen, möchten Sie vielleicht zuerst „How To Make A Drag-and-Drop File Uploader With Vanilla JavaScript“ lesen, ein hervorragendes Tutorial, das vor nicht allzu langer Zeit von Joseph Zimmerman geschrieben wurde.
Die dragenter
, dragleave
, dragover
und drop
Es gibt acht verschiedene Drag-and-Drop-Ereignisse. Jeder wird in einer anderen Phase des Drag-and-Drop-Vorgangs ausgelöst. In diesem Tutorial konzentrieren wir uns auf die vier, die ausgelöst werden, wenn ein Element in eine Dropzone abgelegt wird: dragenter
, dragleave
, dragover
und drop
.
- Das
dragenter
Ereignis wird ausgelöst, wenn ein gezogenes Element in ein gültiges Drop-Ziel eintritt. - Das
dragleave
Ereignis wird ausgelöst, wenn ein gezogenes Element ein gültiges Ablageziel verlässt. - Das
dragover
Ereignis wird ausgelöst, wenn ein gezogenes Element über ein gültiges Ablageziel gezogen wird. (Es wird alle paar hundert Millisekunden ausgelöst.) - Das
drop
-Ereignis wird ausgelöst, wenn ein Element auf ein gültiges Drop-Ziel fällt, dh darüber gezogen und losgelassen wird.
Wir können jedes HTML-Element in ein gültiges Ablageziel verwandeln, indem wir die Ereignishandlerattribute ondragover
und ondrop
definieren.
Sie können alles über die acht Veranstaltungen in den MDN-Webdokumenten erfahren.
Drag-and-Drop-Ereignisse in React
Klonen Sie zunächst das Tutorial-Repository von dieser URL:
https://github.com/chidimo/react-dnd.git
Schauen Sie sich den 01-start
Zweig an. Stellen Sie sicher, dass Sie auch yarn
installiert haben. Sie können es von woolpkg.com erhalten.
Aber wenn Sie es vorziehen, erstellen Sie ein neues React-Projekt und ersetzen Sie den Inhalt von App.js durch den folgenden Code:
import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <h1>React drag-and-drop component</h1> </div> ); } export default App;
Ersetzen Sie außerdem den Inhalt von App.css durch den folgenden CSS-Stil:
.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; }
Wenn Sie das Repo geklont haben, geben Sie die folgenden Befehle (in dieser Reihenfolge) aus, um die App zu starten:
yarn # install dependencies yarn start # start the app
Im nächsten Schritt erstellen Sie eine Drag-and-Drop-Komponente. Erstellen Sie eine Datei DragAndDrop.js im Ordner src/
. Geben Sie die folgende Funktion in die Datei ein:
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;
Im Rückgabe div
haben wir unsere Fokus HTML
-Event-Handler-Attribute definiert. Sie können sehen, dass der einzige Unterschied zu reinem HTML
das Camel-Casing ist.
Das div
ist jetzt ein gültiges Ablageziel, da wir die Ereignishandlerattribute onDragOver
und onDrop
definiert haben.
Wir haben auch Funktionen definiert, um diese Ereignisse zu behandeln. Jede dieser Handlerfunktionen erhält das Ereignisobjekt als Argument.
Für jeden der Event-Handler rufen wir preventDefault()
auf, um den Browser daran zu hindern, sein Standardverhalten auszuführen. Das Standardverhalten des Browsers besteht darin, die abgelegte Datei zu öffnen. Wir rufen auch stopPropagation()
auf, um sicherzustellen, dass das Ereignis nicht von untergeordneten zu übergeordneten Elementen weitergegeben wird.
Importieren Sie die DragAndDrop
Komponente in die App
-Komponente und rendern Sie sie unter der Überschrift.
<div className="App"> <h1>React drag-and-drop component</h1> <DragAndDrop /> </div>
Sehen Sie sich nun die Komponente im Browser an und Sie sollten etwas wie das Bild unten sehen.
Wenn Sie mit dem Repo folgen, ist der entsprechende Zweig 02-start-dragndrop
Zustandsverwaltung mit dem useReducer
Hook
Unser nächster Schritt besteht darin, die Logik für jeden unserer Event-Handler zu schreiben. Bevor wir das tun, müssen wir überlegen, wie wir gelöschte Dateien nachverfolgen wollen. Hier beginnen wir, über staatliche Verwaltung nachzudenken.
Wir werden die folgenden Zustände während des Drag-and-Drop-Vorgangs verfolgen:
-
dropDepth
Dies wird eine ganze Zahl sein. Wir werden es verwenden, um zu verfolgen, wie viele Ebenen tief wir in der Dropzone sind. Später werde ich dies anhand einer Illustration erläutern. ( Credits an Egor Egorov, der mir dieses Licht beleuchtet hat! ) -
inDropZone
Dies wird ein boolescher Wert sein. Wir werden dies verwenden, um zu verfolgen, ob wir uns innerhalb der Dropzone befinden oder nicht. -
FileList
Dies wird eine Liste sein. Wir werden es verwenden, um Dateien zu verfolgen, die in der Dropzone abgelegt wurden.
Um Zustände zu handhaben, stellt React die useState
und useReducer
. Wir entscheiden uns für den useReducer
Hook, da wir es mit Situationen zu tun haben, in denen ein Zustand vom vorherigen Zustand abhängt.
Der Hook useReducer
akzeptiert einen Reducer vom Typ (state, action) => newState
und gibt den aktuellen Status gepaart mit einer dispatch
-Methode zurück.
Weitere Informationen zu useReducer
finden Sie in der React-Dokumentation.
Fügen Sie innerhalb der App
-Komponente (vor der return
-Anweisung) den folgenden Code hinzu:
... 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: [] } ) ...
Der Hook useReducer
akzeptiert zwei Argumente: einen Reducer und einen Anfangszustand. Es gibt den aktuellen Status und eine dispatch
-Funktion zurück, mit der der Status aktualisiert werden kann. Der Status wird aktualisiert, indem eine Aktion gesendet wird, die einen type
und eine optionale Nutzlast enthält. Die am Status der Komponente vorgenommene Aktualisierung hängt davon ab, was von der Case-Anweisung als Ergebnis des Aktionstyps zurückgegeben wird. (Beachten Sie hier, dass unser Anfangszustand ein object
ist.)
Für jede der Zustandsvariablen haben wir eine entsprechende Case-Anweisung definiert, um sie zu aktualisieren. Die Aktualisierung erfolgt durch Aufrufen der von useReducer
zurückgegebenen dispatch
-Funktion.
Übergeben Sie nun data
und DragAndDrop
Sie sie als props
an die dispatch
-Komponente, die Sie in Ihrer App.js -Datei haben:
<DragAndDrop data={data} dispatch={dispatch} />
Oben in der DragAndDrop
Komponente können wir auf beide Werte von props
zugreifen.
const { data, dispatch } = props;
Wenn Sie mit dem Repo folgen, ist der entsprechende Zweig 03-define-reducers
.
Lassen Sie uns die Logik unserer Event-Handler beenden. Beachten Sie, dass die Auslassungspunkte die beiden Zeilen darstellen:
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 }) };
In der folgenden Abbildung haben wir die Drop-Zonen A und B verschachtelt. A ist unsere interessierende Zone. Hier wollen wir auf Drag-and-Drop-Ereignisse lauschen.
Beim Ziehen in eine Dropzone wird jedes Mal, wenn wir eine Grenze treffen, das ondragenter
Ereignis ausgelöst. Dies geschieht an den Grenzen A-in
und B-in
. Da wir die Zone betreten, erhöhen wir dropDepth
.
Ebenso wird beim Herausziehen aus einer Dropzone jedes Mal, wenn wir eine Grenze treffen, das ondragleave
Ereignis ausgelöst. Dies geschieht an den Grenzen A-out
und B-out
. Da wir die Zone verlassen, verringern wir den Wert von dropDepth
. Beachten Sie, dass wir inDropZone
an der Grenze B-out
nicht auf false
setzen. Deshalb haben wir diese Zeile, um dropDepth zu überprüfen und von der Funktion dropDepth
größer als 0
zurückzugeben.
if (data.dropDepth > 0) return
Dies liegt daran, dass wir uns, obwohl das Ereignis ondragleave
ausgelöst wird, immer noch in Zone A befinden. Erst nachdem wir A-out
getroffen haben und dropDepth
jetzt 0
ist, setzen wir inDropZone
auf false
. An dieser Stelle haben wir alle Dropzones verlassen.
const handleDragOver = e => { ... e.dataTransfer.dropEffect = 'copy'; dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: true }); };
Jedes Mal, wenn dieses Ereignis ausgelöst wird, setzen wir inDropZone
auf true
. Das sagt uns, dass wir uns in der Dropzone befinden. Wir setzen auch dropEffect
für das Objekt dataTransfer
auf copy
. Auf einem Mac hat dies den Effekt, dass ein grünes Pluszeichen angezeigt wird, wenn Sie ein Element in der Drop-Zone herumziehen.
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 }); } };
Wir können mit e.dataTransfer.files
auf die abgelegten Dateien zugreifen. Der Wert ist ein Array-ähnliches Objekt, daher verwenden wir die Array-Spread-Syntax, um ihn in ein JavaScript
-Array zu konvertieren.
Wir müssen jetzt prüfen, ob mindestens eine Datei vorhanden ist, bevor wir versuchen, sie zu unserem Dateiarray hinzuzufügen. Wir achten auch darauf, keine Dateien aufzunehmen, die sich bereits in unserer fileList
. Das dataTransfer
Objekt wird in Vorbereitung auf den nächsten Drag-and-Drop-Vorgang gelöscht. Wir setzen auch die Werte von dropDepth
und inDropZone
.
Aktualisieren Sie den className
des div
in der DragAndDrop
Komponente. Dadurch wird der className
des div
abhängig vom Wert von data.inDropZone
bedingt geändert.
<div className={data.inDropZone ? 'drag-drop-zone inside-drag-area' : 'drag-drop-zone'} ... > <p>Drag files here to upload</p> </div>
Rendern Sie die Liste der Dateien in App.js , indem Sie sie über 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>
Versuchen Sie nun, einige Dateien per Drag & Drop in die Dropzone zu ziehen. Sie werden sehen, dass der Hintergrund beim Betreten der Drop-Zone weniger undurchsichtig wird, da die Klasse „ inside-drag-area
“ aktiviert ist.
Wenn Sie die Dateien in der Dropzone freigeben, werden die Dateinamen unter der Dropzone aufgelistet:
Die vollständige Version dieses Tutorials befindet sich im Zweig 04-finish-handlers
.
Fazit
Wir haben gesehen, wie Datei-Uploads in React mithilfe der HTML
-Drag-and-Drop-API gehandhabt werden. Wir haben auch gelernt, wie man den Zustand mit dem useReducer
Hook verwaltet. Wir könnten die Datei handleDrop
Funktion erweitern. Zum Beispiel könnten wir eine weitere Überprüfung hinzufügen, um die Dateigröße zu begrenzen, wenn wir wollten. Dies kann vor oder nach der Prüfung auf vorhandene Dateien erfolgen. Wir könnten die Drop-Zone auch anklickbar machen, ohne die Drag-and-Drop-Funktionalität zu beeinträchtigen.
Ressourcen
- „Hooks-API-Referenz:
useReducer
“, React Docs - „HTML-Drag-and-Drop-API“, MDN-Webdokumentation
- „Beispiele für die Web- und XML-Entwicklung mit dem DOM“, MDN-Webdokumentation
- „Wie man mit Vanilla JavaScript einen Drag-and-Drop-Datei-Uploader erstellt“, Joseph Zimmerman, Smashing Magazine
- „Einfacher Datei-Upload per Drag-and-Drop in React“, Egor Egorov, Medium