Cum să utilizați API-ul HTML Drag-And-Drop în React

Publicat: 2022-03-10
Rezumat rapid ↬ În acest tutorial, vom construi o componentă React drag-and-drop pentru încărcarea fișierelor și imaginilor. În acest proces, vom afla despre API-ul HTML drag-and-drop. De asemenea, vom învăța cum să folosim cârligul useReducer pentru gestionarea stării într-o componentă funcțională React.

API-ul drag-and-drop este una dintre cele mai interesante caracteristici ale HTML. Ne ajută să implementăm funcții de glisare și plasare în browserele web.

În contextul actual, vom trage fișiere din afara browserului. La aruncarea fișierelor, le punem într-o listă și le afișăm numele. Cu fișierele în mână, am putea efectua apoi o altă operațiune asupra fișierului (fișierelor), de exemplu, să le încărcăm pe un server cloud.

În acest tutorial, ne vom concentra asupra modului de implementare a acțiunii de glisare și plasare într-o aplicație React. Dacă ceea ce aveți nevoie este o implementare simplă de JavaScript , poate că ați dori mai întâi să citiți „Cum să faceți un instrument de încărcare de fișiere prin glisare și plasare cu Vanilla JavaScript”, un tutorial excelent scris de Joseph Zimmerman nu cu mult timp în urmă.

Evenimentele dragenter , dragleave , dragover și drop

Există opt evenimente diferite de tip drag-and-drop. Fiecare trage într-o etapă diferită a operațiunii de drag-and-drop. În acest tutorial, ne vom concentra pe cele patru care sunt declanșate atunci când un element este aruncat într-o zonă de drop: dragenter , dragleave , dragover and drop .

  1. Evenimentul dragenter declanșează atunci când un element tras intră într-o țintă de drop validă.
  2. Evenimentul dragleave declanșează atunci când un element tras lasă o țintă de drop validă.
  3. Evenimentul de dragover declanșează atunci când un element tras este tras peste o țintă validă. (Se declanșează la fiecare câteva sute de milisecunde.)
  4. Evenimentul de drop se declanșează atunci când un articol cade pe o țintă validă de drop, adică este tras și eliberat.

Putem transforma orice element HTML într-o țintă de drop validă prin definirea ondragover și ondrop de gestionare a evenimentelor.

Puteți afla totul despre cele opt evenimente din documentele web MDN.

Mai multe după săritură! Continuați să citiți mai jos ↓

Evenimente glisați și plasați în React

Pentru a începe, clonează depozitul tutorial de la această adresă URL:

 https://github.com/chidimo/react-dnd.git

Consultați filiala 01-start . Asigurați-vă că aveți și yarn instalate. Îl puteți obține de pe yarnpkg.com.

Dar dacă preferați, creați un nou proiect React și înlocuiți conținutul App.js cu codul de mai jos:

 import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <h1>React drag-and-drop component</h1> </div> ); } export default App;

De asemenea, înlocuiți conținutul App.css cu stilul CSS de mai jos:

 .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; }

Dacă ați clonat depozitul, lansați următoarele comenzi (în ordine) pentru a porni aplicația:

 yarn # install dependencies yarn start # start the app

Următorul pas este să creați o componentă drag-and-drop. Creați un fișier DragAndDrop.js în folderul src/ . Introduceți următoarea funcție în interiorul fișierului:

 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;

În div -ul returnat, am definit atributele noastre focus HTML de gestionare a evenimentelor. Puteți vedea că singura diferență față de HTML pur este carcasa de cămilă.

div -ul este acum o țintă de eliminare validă, deoarece am definit atributele de gestionare a evenimentelor onDragOver și onDrop .

De asemenea, am definit funcții pentru a gestiona aceste evenimente. Fiecare dintre aceste funcții de gestionare primește obiectul eveniment ca argument.

Pentru fiecare dintre manipulatorii de evenimente, apelăm preventDefault() pentru a opri browserul să-și execute comportamentul implicit. Comportamentul implicit al browserului este deschiderea fișierului aruncat. De asemenea, apelăm stopPropagation() pentru a ne asigura că evenimentul nu este propagat de la elementele copil la elementele părinte.

Importați componenta DragAndDrop în componenta App și redați-o sub titlu.

 <div className="App"> <h1>React drag-and-drop component</h1> <DragAndDrop /> </div>

Acum vizualizați componenta în browser și ar trebui să vedeți ceva ca imaginea de mai jos.

Zona de drop
div pentru a fi convertit într-o zonă de drop (previzualizare mare)

Dacă urmați repo, ramura corespunzătoare este 02-start-dragndrop

Gestionarea stării cu ajutorul cârligului useReducer

Următorul nostru pas va fi să scriem logica pentru fiecare dintre gestionatorii noștri de evenimente. Înainte de a face asta, trebuie să luăm în considerare modul în care intenționăm să ținem evidența fișierelor aruncate. Aici începem să ne gândim la managementul statului.

Vom urmări următoarele stări în timpul operațiunii de glisare și plasare:

  1. dropDepth
    Acesta va fi un număr întreg. Îl vom folosi pentru a ține evidența câte niveluri ne aflăm în zona de drop. Mai târziu, voi explica acest lucru cu o ilustrație. ( Măriți lui Egor Egorov pentru că mi-a adus o lumină pe acesta! )
  2. inDropZone
    Acesta va fi un boolean. Vom folosi acest lucru pentru a ține evidența dacă ne aflăm sau nu în zona de drop.
  3. FileList
    Aceasta va fi o listă. Îl vom folosi pentru a ține evidența fișierelor care au fost aruncate în zona de eliminare.

Pentru a gestiona stările, React oferă cârligele useState și useReducer . Vom opta pentru cârligul useReducer dat fiind că ne vom ocupa de situații în care o stare depinde de starea anterioară.

Cârligul useReducer acceptă un reductor de tip (state, action) => newState și returnează starea curentă asociată cu o metodă de dispatch .

Puteți citi mai multe despre useReducer în documentele React.

În interiorul componentei App (înainte de declarația return ), adăugați următorul cod:

 ... 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: [] } ) ...

Cârligul useReducer acceptă două argumente: un reductor și o stare inițială. Returnează starea curentă și o funcție de dispatch cu care se actualizează starea. Starea este actualizată prin trimiterea unei acțiuni care conține un type și o sarcină utilă opțională. Actualizarea făcută stării componentei depinde de ceea ce este returnat din instrucțiunea case ca urmare a tipului de acțiune. (Rețineți că starea noastră inițială este un object .)

Pentru fiecare dintre variabilele de stare, am definit o declarație case corespunzătoare pentru ao actualiza. Actualizarea este efectuată prin invocarea funcției de dispatch returnată de useReducer .

Acum dispatch data și trimiteți ca elemente de props către componenta DragAndDrop pe care o aveți în fișierul App.js :

 <DragAndDrop data={data} dispatch={dispatch} />

În partea de sus a componentei DragAndDrop , putem accesa ambele valori din props .

 const { data, dispatch } = props;

Dacă urmați repo, ramura corespunzătoare este 03-define-reducers .

Să terminăm logica gestionatorilor noștri de evenimente. Rețineți că punctele de suspensie reprezintă cele două linii:

 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 }) };

În ilustrația care urmează, avem zonele de picături imbricate A și B. A este zona noastră de interes. Aici vrem să ascultăm evenimentele drag-and-drop.

O ilustrare a evenimentelor ondragente și ondragleave
O ilustrare a evenimentelor ondragenter și ondragleave (previzualizare mare)

Când trageți într-o zonă de drop, de fiecare dată când atingem o limită, evenimentul ondragenter este declanșat. Acest lucru se întâmplă la granițele A-in și B-in . Din moment ce intrăm în zonă, dropDepth .

De asemenea, atunci când trageți dintr-o zonă de drop, de fiecare dată când atingem o limită, evenimentul ondragleave este declanșat. Acest lucru se întâmplă la granițele A-out și B-out . Deoarece părăsim zona, reducem valoarea dropDepth . Observați că nu setăm inDropZone la false la limita B-out . De aceea avem această linie pentru a verifica dropDepth și a reveni de la funcția dropDepth mai mare decât 0 .

 if (data.dropDepth > 0) return

Acest lucru se datorează faptului că, deși evenimentul ondragleave este declanșat, suntem încă în zona A. Abia după ce am apăsat A-out , iar dropDepth este acum 0 când setăm inDropZone la false . În acest moment, am părăsit toate zonele de drop.

 const handleDragOver = e => { ... e.dataTransfer.dropEffect = 'copy'; dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: true }); };

De fiecare dată când se declanșează acest eveniment, setăm inDropZone la true . Acest lucru ne spune că ne aflăm în zona de drop. De asemenea, setăm dropEffect pe obiectul dataTransfercopy . Pe un Mac, acest lucru are ca efect afișarea unui semn verde plus în timp ce trageți un element în zona de fixare.

 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 }); } };

Putem accesa fișierele aruncate cu e.dataTransfer.files . Valoarea este un obiect asemănător matricei, așa că folosim sintaxa de răspândire a matricei pentru a o converti într-o matrice JavaScript .

Acum trebuie să verificăm dacă există cel puțin un fișier înainte de a încerca să-l adăugăm la gama noastră de fișiere. De asemenea, ne asigurăm că nu includem fișiere care sunt deja în lista noastră de fileList . Obiectul de dataTransfer de date este șters în pregătirea următoarei operațiuni de drag-and-drop. De asemenea, resetăm valorile dropDepth și inDropZone .

Actualizați className al div -ului în componenta DragAndDrop . Acest lucru va schimba în mod condiționat className al div -ului în funcție de valoarea data.inDropZone .

 <div className={data.inDropZone ? 'drag-drop-zone inside-drag-area' : 'drag-drop-zone'} ... > <p>Drag files here to upload</p> </div>

Redați lista de fișiere în App.js prin maparea prin 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>

Acum încercați să trageți și să plasați unele fișiere în zona de plasare. Veți vedea că, pe măsură ce intrăm în zona de drop, fundalul devine mai puțin opac deoarece este activată clasa de inside-drag-area .

Când eliberați fișierele în interiorul zonei de drop, veți vedea numele fișierelor listate sub zona de drop:

Zona de aruncare care arată o opacitate scăzută în timpul tragerii
Zona de fixare care arată o opacitate scăzută în timpul tragerii (previzualizare mare)
O listă de fișiere introduse în zona de drop
O listă de fișiere introduse în zona de plasare (previzualizare mare)

Versiunea completă a acestui tutorial se află pe ramura 04-finish-handlers .

Concluzie

Am văzut cum să gestionăm încărcările de fișiere în React folosind API-ul HTML drag-and-drop. De asemenea, am învățat cum să gestionăm starea cu ajutorul cârligului useReducer . Am putea extinde funcția de fișier handleDrop . De exemplu, am putea adăuga o altă verificare pentru a limita dimensiunile fișierelor dacă dorim. Acest lucru poate veni înainte sau după verificarea fișierelor existente. De asemenea, am putea face clic pe zona de fixare fără a afecta funcționalitatea de glisare și plasare.

Resurse

  • „Hooks API Reference: useReducer ”, React Docs
  • „HTML Drag-and-Drop API”, documente web MDN
  • „Exemple de dezvoltare web și XML folosind DOM”, documentează MDN web
  • „Cum să faci un instrument de încărcare de fișiere prin glisare și plasare cu Vanilla JavaScript”, Joseph Zimmerman, Smashing Magazine
  • „Încărcare simplă de fișiere prin glisare și plasare în React”, Egor Egorov, mediu