So verwenden Sie die HTML-Drag-and-Drop-API in React

Veröffentlicht: 2022-03-10
Kurze Zusammenfassung ↬ In diesem Tutorial erstellen wir eine React-Drag-and-Drop-Komponente für das Hochladen von Dateien und Bildern. Dabei lernen wir die HTML-Drag-and-Drop-API kennen. Wir werden auch lernen, wie man den useReducer-Hook zum Verwalten des Zustands in einer React-Funktionskomponente verwendet.

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

  1. Das dragenter Ereignis wird ausgelöst, wenn ein gezogenes Element in ein gültiges Drop-Ziel eintritt.
  2. Das dragleave Ereignis wird ausgelöst, wenn ein gezogenes Element ein gültiges Ablageziel verlässt.
  3. 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.)
  4. 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.

Mehr nach dem Sprung! Lesen Sie unten weiter ↓

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.

Abwurfgebiet
div , das in eine Dropzone umgewandelt werden soll (große Vorschau)

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:

  1. 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! )
  2. inDropZone
    Dies wird ein boolescher Wert sein. Wir werden dies verwenden, um zu verfolgen, ob wir uns innerhalb der Dropzone befinden oder nicht.
  3. 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.

Eine Illustration der ondragenter- und ondragleave-Ereignisse
Eine Illustration der ondragenter und ondragleave Ereignisse (große Vorschau)

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:

Drop-Zone mit geringer Deckkraft während des Ziehens
Drop-Zone mit geringer Deckkraft beim Dragover (große Vorschau)
Eine Liste von Dateien, die in die Dropzone abgelegt wurden
Eine Liste der Dateien, die in der Dropzone abgelegt wurden (große Vorschau)

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