Come utilizzare l'API di trascinamento della selezione HTML in React
Pubblicato: 2022-03-10L'API drag-and-drop è una delle funzionalità più interessanti dell'HTML. Ci aiuta a implementare le funzionalità di trascinamento della selezione nei browser Web.
Nel contesto attuale, trascineremo i file dall'esterno del browser. Dopo aver rilasciato i file, li inseriamo in un elenco e mostriamo i loro nomi. Con i file in mano, potremmo quindi eseguire altre operazioni sui file, ad esempio caricarli su un server cloud.
In questo tutorial, ci concentreremo su come implementare l'azione di trascinamento e rilascio in un'applicazione React. Se ciò di cui hai bisogno è una semplice implementazione JavaScript
, forse ti piacerebbe prima leggere "Come creare un caricatore di file drag-and-drop con Vanilla JavaScript", un eccellente tutorial scritto da Joseph Zimmerman non molto tempo fa.
Gli dragenter
, dragleave
, dragover
e drop
Esistono otto diversi eventi di trascinamento della selezione. Ognuno si attiva in una fase diversa dell'operazione di trascinamento della selezione. In questo tutorial, ci concentreremo sui quattro che vengono attivati quando un elemento viene rilasciato in una zona di rilascio: dragenter
, dragleave
, dragover
e drop
.
- L'evento
dragenter
si attiva quando un elemento trascinato entra in una destinazione di rilascio valida. - L'evento
dragleave
si attiva quando un elemento trascinato lascia una destinazione di rilascio valida. - L'evento di trascinamento si
dragover
quando un elemento trascinato viene trascinato su una destinazione di rilascio valida. (Si attiva ogni poche centinaia di millisecondi.) - L'evento
drop
si attiva quando un oggetto cade su un bersaglio di rilascio valido, ovvero trascinato e rilasciato.
Possiamo trasformare qualsiasi elemento HTML in una destinazione di rilascio valida definendo gli attributi del gestore di eventi ondragover
e ondrop
.
Puoi imparare tutto sugli otto eventi dai documenti web di MDN.
Eventi drag-and-drop in reazione
Per iniziare, clona il repository del tutorial da questo URL:
https://github.com/chidimo/react-dnd.git
Dai un'occhiata al ramo 01-start
. Assicurati di aver installato anche il yarn
. Puoi ottenerlo da yarnpkg.com.
Ma se preferisci, crea un nuovo progetto React e sostituisci il contenuto di App.js con il codice seguente:
import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <h1>React drag-and-drop component</h1> </div> ); } export default App;
Inoltre, sostituisci il contenuto di App.css con il seguente stile 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; }
Se hai clonato il repository, emetti i seguenti comandi (in ordine) per avviare l'app:
yarn # install dependencies yarn start # start the app
Il passaggio successivo consiste nel creare un componente di trascinamento della selezione. Crea un file DragAndDrop.js all'interno della cartella src/
. Immettere la seguente funzione all'interno del file:
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;
In return div
, abbiamo definito gli attributi del gestore di eventi HTML
focus. Puoi vedere che l'unica differenza rispetto HTML
puro è l'involucro del cammello.
Il div
è ora una destinazione di rilascio valida poiché abbiamo definito gli attributi del gestore eventi onDragOver
e onDrop
.
Abbiamo anche definito funzioni per gestire quegli eventi. Ciascuna di queste funzioni di gestione riceve l'oggetto evento come argomento.
Per ciascuno dei gestori di eventi, chiamiamo preventDefault()
per impedire al browser di eseguire il suo comportamento predefinito. Il comportamento predefinito del browser consiste nell'aprire il file eliminato. Chiamiamo anche stopPropagation()
per assicurarci che l'evento non venga propagato da elementi figlio a elementi padre.
Importa il componente DragAndDrop
nel componente App
e visualizzalo sotto l'intestazione.
<div className="App"> <h1>React drag-and-drop component</h1> <DragAndDrop /> </div>
Ora visualizza il componente nel browser e dovresti vedere qualcosa come l'immagine qui sotto.
Se stai seguendo con il repository, il ramo corrispondente è 02-start-dragndrop
Gestione dello stato con il gancio useReducer
Il nostro prossimo passo sarà scrivere la logica per ciascuno dei nostri gestori di eventi. Prima di farlo, dobbiamo considerare come intendiamo tenere traccia dei file eliminati. È qui che iniziamo a pensare alla gestione dello stato.
Terremo traccia dei seguenti stati durante l'operazione di trascinamento della selezione:
-
dropDepth
Questo sarà un numero intero. Lo useremo per tenere traccia di quanti livelli ci troviamo nella zona di rilascio. Più avanti, lo spiegherò con un'illustrazione. ( Ringraziamenti a Egor Egorov per aver fatto luce su questo per me! ) -
inDropZone
Questo sarà un booleano. Lo useremo per tenere traccia del fatto che siamo all'interno della zona di rilascio o meno. -
FileList
Questa sarà una lista. Lo useremo per tenere traccia dei file che sono stati rilasciati nella zona di rilascio.
Per gestire gli stati, React fornisce gli useState
e useReducer
. Opteremo per l'hook useReducer
dato che avremo a che fare con situazioni in cui uno stato dipende dallo stato precedente.
L'hook useReducer
accetta un riduttore di tipo (state, action) => newState
e restituisce lo stato corrente abbinato a un metodo di dispatch
.
Puoi leggere di più su useReducer
nei documenti React.
All'interno del componente App
(prima dell'istruzione return
), aggiungi il codice seguente:
... 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: [] } ) ...
L'hook useReducer
accetta due argomenti: un riduttore e uno stato iniziale. Restituisce lo stato corrente e una funzione di dispatch
con cui aggiornare lo stato. Lo stato viene aggiornato inviando un'azione che contiene un type
e un payload facoltativo. L'aggiornamento apportato allo stato del componente dipende da ciò che viene restituito dall'istruzione case come risultato del tipo di azione. (Nota qui che il nostro stato iniziale è un object
.)
Per ciascuna delle variabili di stato, abbiamo definito un'istruzione case corrispondente per aggiornarla. L'aggiornamento viene eseguito richiamando la funzione di dispatch
restituita da useReducer
.
Ora passa data
e dispatch
come props
di scena al componente DragAndDrop
che hai nel tuo file App.js :
<DragAndDrop data={data} dispatch={dispatch} />
Nella parte superiore del componente DragAndDrop
, possiamo accedere a entrambi i valori da props
.
const { data, dispatch } = props;
Se stai seguendo con il repository, il ramo corrispondente è 03-define-reducers
.
Concludiamo la logica dei nostri gestori di eventi. Si noti che i puntini di sospensione rappresentano le due linee:
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 }) };
Nell'illustrazione che segue, abbiamo nidificato le zone di rilascio A e B. A è la nostra zona di interesse. È qui che vogliamo ascoltare gli eventi di trascinamento della selezione.
Quando si trascina in una zona di rilascio, ogni volta che si raggiunge un limite, viene attivato l'evento ondragenter
. Questo accade ai confini A-in
e B-in
. Poiché stiamo entrando nella zona, incrementiamo dropDepth
.
Allo stesso modo, quando si trascina fuori da una zona di rilascio, ogni volta che si colpisce un confine, viene attivato l'evento ondragleave
. Questo accade ai confini A-out
e B-out
. Poiché stiamo uscendo dalla zona, decrementiamo il valore di dropDepth
. Si noti che non impostiamo inDropZone
su false
al confine B-out
. Ecco perché abbiamo questa riga per controllare dropDepth e restituire dalla funzione dropDepth
maggiore di 0
.
if (data.dropDepth > 0) return
Questo perché anche se l'evento ondragleave
viene attivato, siamo ancora all'interno della zona A. È solo dopo aver colpito A-out
e dropDepth
è ora 0
che impostiamo inDropZone
su false
. A questo punto, abbiamo lasciato tutte le zone di rilascio.
const handleDragOver = e => { ... e.dataTransfer.dropEffect = 'copy'; dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: true }); };
Ogni volta che questo evento viene attivato, impostiamo inDropZone
su true
. Questo ci dice che siamo all'interno della zona di rilascio. Impostiamo anche dropEffect
sull'oggetto dataTransfer
da copy
. Su un Mac, questo ha l'effetto di mostrare un segno più verde mentre trascini un elemento nella zona di rilascio.
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 }); } };
Possiamo accedere ai file eliminati con e.dataTransfer.files
. Il valore è un oggetto simile a un array, quindi utilizziamo la sintassi di diffusione dell'array per convertirlo in un array JavaScript
.
Ora dobbiamo verificare se c'è almeno un file prima di tentare di aggiungerlo al nostro array di file. Ci assicuriamo inoltre di non includere file che sono già nel nostro fileList
. L'oggetto dataTransfer
viene cancellato in preparazione per la successiva operazione di trascinamento della selezione. Ripristiniamo anche i valori di dropDepth
e inDropZone
.
Aggiorna il className
del div
nel componente DragAndDrop
. Ciò cambierà in modo condizionale il className
del div
a seconda del valore di data.inDropZone
.
<div className={data.inDropZone ? 'drag-drop-zone inside-drag-area' : 'drag-drop-zone'} ... > <p>Drag files here to upload</p> </div>
Eseguire il rendering dell'elenco di file in App.js mappando tramite 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>
Ora prova a trascinare e rilasciare alcuni file nell'area di rilascio. Vedrai che quando entriamo nella zona di rilascio, lo sfondo diventa meno opaco perché la classe inside-drag-area
è attivata.
Quando rilasci i file all'interno dell'area di rilascio, vedrai i nomi dei file elencati sotto l'area di rilascio:
La versione completa di questo tutorial si trova nel ramo 04-finish-handlers
.
Conclusione
Abbiamo visto come gestire i caricamenti di file in React utilizzando l'API di trascinamento della selezione HTML
. Abbiamo anche imparato a gestire lo stato con l'hook useReducer
. Potremmo estendere la funzione handleDrop
del file. Ad esempio, potremmo aggiungere un altro controllo per limitare le dimensioni dei file, se lo desideriamo. Questo può avvenire prima o dopo il controllo dei file esistenti. Potremmo anche rendere selezionabile la zona di rilascio senza influire sulla funzionalità di trascinamento della selezione.
Risorse
- "Riferimento API Hooks:
useReducer
", React Docs - "API di trascinamento della selezione HTML", documenti Web MDN
- "Esempi di sviluppo Web e XML utilizzando il DOM", documenti Web MDN
- "Come creare un caricatore di file drag-and-drop con JavaScript Vanilla", Joseph Zimmerman, Smashing Magazine
- "Semplice caricamento di file drag-and-drop in reazione", Egor Egorov, Medium