Cara Menggunakan Drag-And-Drop API HTML di React
Diterbitkan: 2022-03-10Drag-and-drop API adalah salah satu fitur paling keren dari HTML. Ini membantu kami menerapkan fitur drag-and-drop di browser web.
Dalam konteks saat ini, kami akan menyeret file dari luar browser. Saat menjatuhkan file, kami memasukkannya ke dalam daftar dan menampilkan namanya. Dengan file di tangan, kami kemudian dapat melakukan beberapa operasi lain pada file, misalnya mengunggahnya ke server cloud.
Dalam tutorial ini, kita akan fokus pada bagaimana mengimplementasikan aksi drag and drop dalam aplikasi React. Jika yang Anda butuhkan adalah implementasi JavaScript
biasa, mungkin Anda pertama-tama ingin membaca “Cara Membuat Pengunggah File Seret-Dan-Lepaskan Dengan JavaScript Vanilla”, tutorial luar biasa yang ditulis oleh Joseph Zimmerman belum lama ini.
dragenter
, dragleave
, dragover
, Dan drop
Acara
Ada delapan acara drag-and-drop yang berbeda. Masing-masing menembak pada tahap yang berbeda dari operasi drag-and-drop. Dalam tutorial ini, kita akan fokus pada empat yang ditembakkan ketika sebuah item dijatuhkan ke zona drop: dragenter
, dragleave
, dragover
dan drop
.
- Acara
dragenter
diaktifkan ketika item yang diseret memasuki target jatuh yang valid. - Acara
dragleave
diaktifkan ketika item yang diseret meninggalkan target jatuh yang valid. - Acara
dragover
diaktifkan ketika item yang diseret sedang diseret ke atas target penurunan yang valid. (Ini menyala setiap beberapa ratus milidetik.) - Event
drop
akan aktif ketika item jatuh pada target drop yang valid, yaitu diseret dan dilepaskan.
Kita dapat mengubah elemen HTML apa pun menjadi target penurunan yang valid dengan mendefinisikan atribut event handler ondragover
dan ondrop
.
Anda dapat mempelajari semua tentang delapan peristiwa dari dokumen web MDN.
Acara Drag-And-Drop di React
Untuk memulai, kloning repo tutorial dari URL ini:
https://github.com/chidimo/react-dnd.git
Lihat cabang 01-start
. Pastikan Anda telah memasang yarn
juga. Anda bisa mendapatkannya dari yarnpkg.com.
Tetapi jika Anda mau, buat proyek React baru dan ganti konten App.js dengan kode di bawah ini:
import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <h1>React drag-and-drop component</h1> </div> ); } export default App;
Juga, ganti konten App.css dengan gaya CSS di bawah ini:
.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; }
Jika Anda mengkloning repo, jalankan perintah berikut (secara berurutan) untuk memulai aplikasi:
yarn # install dependencies yarn start # start the app
Langkah selanjutnya adalah membuat komponen drag-and-drop. Buat file DragAndDrop.js di dalam folder src/
. Masukkan fungsi berikut di dalam 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;
Dalam return div
, kita telah mendefinisikan atribut event handler HTML
fokus kita. Anda dapat melihat bahwa satu-satunya perbedaan dari HTML
murni adalah casing unta.
div
sekarang menjadi target penurunan yang valid karena kita telah mendefinisikan atribut event handler onDragOver
dan onDrop
.
Kami juga mendefinisikan fungsi untuk menangani peristiwa tersebut. Masing-masing fungsi handler ini menerima objek event sebagai argumennya.
Untuk setiap event handler, kami memanggil preventDefault()
untuk menghentikan browser menjalankan perilaku defaultnya. Perilaku browser default adalah membuka file yang dijatuhkan. Kami juga memanggil stopPropagation()
untuk memastikan bahwa acara tidak disebarkan dari elemen anak ke elemen induk.
Impor komponen DragAndDrop
ke dalam komponen App
dan render di bawah heading.
<div className="App"> <h1>React drag-and-drop component</h1> <DragAndDrop /> </div>
Sekarang lihat komponen di browser dan Anda akan melihat sesuatu seperti gambar di bawah ini.
Jika Anda mengikuti repo, cabang yang sesuai adalah 02-start-dragndrop
Mengelola Status Dengan useReducer
Langkah kita selanjutnya adalah menulis logika untuk setiap event handler kita. Sebelum kita melakukannya, kita harus mempertimbangkan bagaimana kita berniat untuk melacak file yang dijatuhkan. Di sinilah kita mulai berpikir tentang pengelolaan negara.
Kami akan melacak status berikut selama operasi drag-and-drop:
-
dropDepth
Ini akan menjadi bilangan bulat. Kami akan menggunakannya untuk melacak seberapa dalam level kami di zona degradasi. Nanti akan saya jelaskan dengan ilustrasi. ( Kredit untuk Egor Egorov karena menyoroti yang satu ini untuk saya! ) -
inDropZone
Ini akan menjadi boolean. Kami akan menggunakan ini untuk melacak apakah kami berada di dalam zona degradasi atau tidak. -
FileList
Ini akan menjadi daftar. Kami akan menggunakannya untuk melacak file yang telah dijatuhkan ke zona drop.
Untuk menangani status, React menyediakan kait useState
dan useReducer
. Kami akan memilih kait useReducer
mengingat bahwa kami akan berurusan dengan situasi di mana suatu keadaan bergantung pada keadaan sebelumnya.
Kait useReducer
menerima peredam tipe (state, action) => newState
, dan mengembalikan status saat ini yang dipasangkan dengan metode dispatch
.
Anda dapat membaca lebih lanjut tentang useReducer
di dokumen React.
Di dalam komponen App
(sebelum pernyataan return
), tambahkan kode berikut:
... 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: [] } ) ...
Kait useReducer
menerima dua argumen: peredam dan status awal. Ini mengembalikan status saat ini dan fungsi dispatch
untuk memperbarui status. Status diperbarui dengan mengirimkan tindakan yang berisi type
dan muatan opsional. Pembaruan yang dibuat untuk status komponen bergantung pada apa yang dikembalikan dari pernyataan kasus sebagai hasil dari jenis tindakan. (Perhatikan di sini bahwa status awal kita adalah object
.)
Untuk setiap variabel status, kami mendefinisikan pernyataan kasus yang sesuai untuk memperbaruinya. Pembaruan dilakukan dengan menjalankan fungsi dispatch
yang dikembalikan oleh useReducer
.
Sekarang berikan data
dan DragAndDrop
sebagai props
ke komponen dispatch
yang Anda miliki di file App.js Anda:
<DragAndDrop data={data} dispatch={dispatch} />
Di bagian atas komponen DragAndDrop
, kita dapat mengakses kedua nilai dari props
.
const { data, dispatch } = props;
Jika Anda mengikuti repo, cabang yang sesuai adalah 03-define-reducers
.
Mari selesaikan logika event handler kita. Perhatikan bahwa elipsis mewakili dua garis:
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 }) };
Dalam ilustrasi berikut, kami memiliki zona penurunan bersarang A dan B. A adalah zona yang kami minati. Di sinilah kami ingin mendengarkan acara drag-and-drop.
Saat menyeret ke zona jatuh, setiap kali kita mencapai batas, peristiwa ondragenter
. Ini terjadi pada batas A-in
dan B-in
. Karena kita memasuki zona, kita menambahkan dropDepth
.
Demikian juga, ketika menyeret keluar dari zona jatuh, setiap kali kita mencapai batas, peristiwa ondragleave
. Hal ini terjadi pada batas A-out
dan B-out
. Karena kita meninggalkan zona, kita mengurangi nilai dropDepth
. Perhatikan bahwa kita tidak menyetel inDropZone
ke false
pada batas B-out
. Itulah mengapa kami memiliki baris ini untuk memeriksa dropDepth dan kembali dari fungsi dropDepth
lebih besar dari 0
.
if (data.dropDepth > 0) return
Ini karena meskipun event ondragleave
diaktifkan, kita masih berada dalam zona A. Hanya setelah kita menekan A-out
, dan dropDepth
sekarang 0
kita menetapkan inDropZone
ke false
. Pada titik ini, kami telah meninggalkan semua zona degradasi.
const handleDragOver = e => { ... e.dataTransfer.dropEffect = 'copy'; dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: true }); };
Setiap kali acara ini diaktifkan, kami menyetel inDropZone
ke true
. Ini memberitahu kita bahwa kita berada di dalam zona drop. Kami juga mengatur dropEffect
pada objek dataTransfer
untuk copy
. Di Mac, ini memiliki efek menampilkan tanda plus hijau saat Anda menyeret item di sekitar zona drop.
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 }); } };
Kami dapat mengakses file yang dijatuhkan dengan e.dataTransfer.files
. Nilainya adalah objek seperti array, jadi kami menggunakan sintaks penyebaran array untuk mengubahnya menjadi array JavaScript
.
Kita sekarang perlu memeriksa apakah ada setidaknya satu file sebelum mencoba menambahkannya ke array file kita. Kami juga memastikan untuk tidak menyertakan file yang sudah ada di fileList
kami. Objek dataTransfer
dibersihkan sebagai persiapan untuk operasi drag-and-drop berikutnya. Kami juga mengatur ulang nilai dropDepth
dan inDropZone
.
Perbarui className
div
di komponen DragAndDrop
. Ini secara kondisional akan mengubah className
div
tergantung pada nilai data.inDropZone
.
<div className={data.inDropZone ? 'drag-drop-zone inside-drag-area' : 'drag-drop-zone'} ... > <p>Drag files here to upload</p> </div>
Render daftar file di App.js dengan memetakan melalui 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>
Sekarang coba drag dan drop beberapa file ke zona drop. Anda akan melihat bahwa saat kita memasuki zona jatuh, latar belakang menjadi kurang buram karena kelas inside-drag-area
diaktifkan.
Saat Anda melepaskan file di dalam drop zone, Anda akan melihat nama file yang tercantum di bawah drop zone:
Versi lengkap dari tutorial ini ada di cabang 04-finish-handlers
.
Kesimpulan
Kita telah melihat bagaimana menangani unggahan file di React menggunakan API drag-and-drop HTML
. Kami juga telah mempelajari cara mengelola status dengan kait useReducer
. Kita dapat memperluas fungsi file handleDrop
. Misalnya, kita dapat menambahkan tanda centang lain untuk membatasi ukuran file jika kita mau. Ini bisa terjadi sebelum atau sesudah pemeriksaan file yang ada. Kami juga dapat membuat zona drop dapat diklik tanpa mempengaruhi fungsionalitas drag-and-drop.
Sumber daya
- “Referensi Hooks API:
useReducer
,” React Docs - “HTML Drag-and-Drop API,” dokumen web MDN
- “Contoh Pengembangan Web Dan XML Menggunakan DOM,” dokumen web MDN
- “Cara Membuat Pengunggah File Seret-dan-Lepaskan Dengan JavaScript Vanilla,” Joseph Zimmerman, Majalah Smashing
- “Unggah File Seret-Dan-Lepaskan Sederhana Dalam Bereaksi,” Egor Egorov, Medium