Cara Menggunakan Drag-And-Drop API HTML di React

Diterbitkan: 2022-03-10
Ringkasan cepat Dalam tutorial ini, kita akan membuat komponen React drag-and-drop untuk upload file dan gambar. Dalam prosesnya, kita akan belajar tentang HTML drag-and-drop API. Kita juga akan mempelajari cara menggunakan hook useReducer untuk mengelola status dalam komponen fungsional React.

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

  1. Acara dragenter diaktifkan ketika item yang diseret memasuki target jatuh yang valid.
  2. Acara dragleave diaktifkan ketika item yang diseret meninggalkan target jatuh yang valid.
  3. Acara dragover diaktifkan ketika item yang diseret sedang diseret ke atas target penurunan yang valid. (Ini menyala setiap beberapa ratus milidetik.)
  4. 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.

Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

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.

Zona jatuh
div untuk diubah menjadi zona drop (Pratinjau besar)

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:

  1. 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! )
  2. inDropZone
    Ini akan menjadi boolean. Kami akan menggunakan ini untuk melacak apakah kami berada di dalam zona degradasi atau tidak.
  3. 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.

Ilustrasi acara ondragenter dan ondragleave
Ilustrasi acara ondragenter dan ondragleave (Pratinjau besar)

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:

Drop zone menunjukkan opacity rendah selama dragover
Drop zone menunjukkan opacity rendah selama dragover (Pratinjau besar)
Daftar file jatuh ke zona drop
Daftar file jatuh ke zona drop (Pratinjau besar)

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