Come utilizzare l'API di trascinamento della selezione HTML in React

Pubblicato: 2022-03-10
Riepilogo rapido ↬ In questo tutorial, creeremo un componente di trascinamento della selezione di React per il caricamento di file e immagini. Nel processo, impareremo a conoscere l'API di trascinamento della selezione HTML. Impareremo anche come utilizzare l'hook useReducer per gestire lo stato in un componente funzionale React.

L'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 .

  1. L'evento dragenter si attiva quando un elemento trascinato entra in una destinazione di rilascio valida.
  2. L'evento dragleave si attiva quando un elemento trascinato lascia una destinazione di rilascio valida.
  3. 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.)
  4. 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.

Altro dopo il salto! Continua a leggere sotto ↓

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.

Zona di rilascio
div da convertire in una zona di rilascio (Anteprima grande)

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:

  1. 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! )
  2. inDropZone
    Questo sarà un booleano. Lo useremo per tenere traccia del fatto che siamo all'interno della zona di rilascio o meno.
  3. 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.

Un'illustrazione degli eventi ondragenter e ondragleave
Un'illustrazione degli eventi ondragenter e ondragleave (anteprima grande)

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:

Zona di rilascio che mostra una bassa opacità durante il trascinamento
Zona di rilascio che mostra una bassa opacità durante il trascinamento (anteprima grande)
Un elenco di file rilasciati nell'area di rilascio
Un elenco di file rilasciati nell'area di rilascio (anteprima grande)

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