Jak korzystać z API przeciągania i upuszczania HTML w React
Opublikowany: 2022-03-10Interfejs API typu „przeciągnij i upuść” to jedna z najfajniejszych funkcji HTML. Pomaga nam zaimplementować funkcje przeciągania i upuszczania w przeglądarkach internetowych.
W obecnym kontekście będziemy przeciągać pliki spoza przeglądarki. Po upuszczeniu pliku(ów) umieszczamy je na liście i wyświetlamy ich nazwy. Mając pliki w ręku, moglibyśmy następnie wykonać na pliku (plikach) jakąś inną operację, np. przesłać je na serwer w chmurze.
W tym samouczku skupimy się na tym, jak zaimplementować akcję przeciągania i upuszczania w aplikacji React. Jeśli potrzebujesz prostej implementacji JavaScript
, być może najpierw chciałbyś przeczytać „Jak zrobić program do przesyłania plików metodą „przeciągnij i upuść” za pomocą Vanilla JavaScript”, doskonały samouczek napisany niedawno przez Josepha Zimmermana.
dragenter
, dragleave
, dragover
i drop
zdarzenia
Istnieje osiem różnych zdarzeń przeciągania i upuszczania. Każdy z nich uruchamia się na innym etapie operacji przeciągania i upuszczania. W tym samouczku skupimy się na czterech, które są uruchamiane, gdy element jest upuszczany do strefy upuszczania: dragenter
, dragleave
, dragover
i drop
.
- Zdarzenie
dragenter
uruchamiane, gdy przeciągnięty element wejdzie w prawidłowy cel upuszczania. - Zdarzenie
dragleave
uruchamiane, gdy przeciągany element pozostawia prawidłowy cel upuszczania. - Zdarzenie
dragover
uruchamiane, gdy przeciągany element jest przeciągany nad prawidłowym celem upuszczania. (Odpala co kilkaset milisekund.) - Zdarzenie
drop
jest uruchamiane, gdy przedmiot spadnie na prawidłowy cel upuszczenia, tj. zostanie przeciągnięty i zwolniony.
Możemy zmienić dowolny element HTML w prawidłowy cel upuszczania, definiując atrybuty obsługi zdarzeń ondragover
i ondrop
.
Możesz dowiedzieć się wszystkiego o ośmiu wydarzeniach z dokumentacji internetowej MDN.
Zdarzenia typu „przeciągnij i upuść” w React
Aby rozpocząć, sklonuj repozytorium samouczka z tego adresu URL:
https://github.com/chidimo/react-dnd.git
Sprawdź gałąź 01-start
. Upewnij się, że masz zainstalowaną yarn
. Możesz go pobrać ze strony internetowej yarnpkg.com.
Ale jeśli wolisz, utwórz nowy projekt React i zastąp zawartość App.js poniższym kodem:
import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <h1>React drag-and-drop component</h1> </div> ); } export default App;
Zastąp również zawartość App.css poniższym stylem 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; }
Jeśli sklonowałeś repozytorium, wydaj następujące polecenia (w kolejności), aby uruchomić aplikację:
yarn # install dependencies yarn start # start the app
Następnym krokiem jest utworzenie komponentu typu „przeciągnij i upuść”. Utwórz plik DragAndDrop.js w folderze src/
. Wprowadź następującą funkcję w pliku:
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;
W zwracanym div
zdefiniowaliśmy nasze atrybuty obsługi zdarzeń w HTML
. Widać, że jedyną różnicą w stosunku do czystego HTML
jest obudowa wielbłąda.
Element div
jest teraz prawidłowym miejscem docelowym upuszczania, ponieważ zdefiniowaliśmy atrybuty obsługi zdarzeń onDragOver
i onDrop
.
Zdefiniowaliśmy również funkcje do obsługi tych zdarzeń. Każda z tych funkcji obsługi otrzymuje obiekt zdarzenia jako swój argument.
Dla każdego z programów obsługi zdarzeń wywołujemy preventDefault()
, aby uniemożliwić przeglądarce wykonywanie jej domyślnego zachowania. Domyślnym zachowaniem przeglądarki jest otwarcie upuszczonego pliku. Wywołujemy również stopPropagation()
, aby upewnić się, że zdarzenie nie jest propagowane z elementów potomnych do elementów nadrzędnych.
Zaimportuj składnik DragAndDrop
do składnika App
i wyrenderuj go poniżej nagłówka.
<div className="App"> <h1>React drag-and-drop component</h1> <DragAndDrop /> </div>
Teraz wyświetl komponent w przeglądarce i powinieneś zobaczyć coś takiego jak na poniższym obrazku.
Jeśli śledzisz repozytorium, odpowiednia gałąź to 02-start-dragndrop
Zarządzanie stanem za pomocą useReducer
Naszym następnym krokiem będzie napisanie logiki dla każdego z naszych programów obsługi zdarzeń. Zanim to zrobimy, musimy zastanowić się, w jaki sposób zamierzamy śledzić porzucone pliki. Tu zaczynamy myśleć o zarządzaniu państwem.
Podczas operacji przeciągania i upuszczania będziemy śledzić następujące stany:
-
dropDepth
To będzie liczba całkowita. Użyjemy go, aby śledzić, ile poziomów głęboko znajdujemy się w strefie zrzutu. Później wyjaśnię to na ilustracji. ( Podziękowania dla Egora Egorova za oświecenie tego dla mnie! ) -
inDropZone
To będzie wartość logiczna. Użyjemy tego, aby śledzić, czy jesteśmy w strefie zrzutu, czy nie. -
FileList
To będzie lista. Użyjemy go do śledzenia plików, które zostały upuszczone do strefy zrzutu.
Do obsługi stanów React udostępnia haki useState
i useReducer
. Zdecydujemy się na hak useReducer
, ponieważ będziemy mieli do czynienia z sytuacjami, w których stan zależy od stanu poprzedniego.
Hak useReducer
akceptuje reduktor typu (state, action) => newState
i zwraca aktualny stan sparowany z metodą dispatch
.
Możesz przeczytać więcej o useReducer
w dokumentacji React.
Wewnątrz komponentu App
(przed instrukcją return
) dodaj następujący kod:
... 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: [] } ) ...
Hak useReducer
przyjmuje dwa argumenty: reduktor i stan początkowy. Zwraca bieżący stan i funkcję dispatch
, za pomocą której aktualizuje się stan. Stan jest aktualizowany przez wywołanie akcji zawierającej type
i opcjonalny ładunek. Aktualizacja stanu składnika zależy od tego, co zostanie zwrócone z instrukcji case w wyniku typu akcji. (Zauważ, że naszym stanem początkowym jest object
).
Dla każdej ze zmiennych stanu zdefiniowaliśmy odpowiednią instrukcję case, aby ją zaktualizować. Aktualizacja odbywa się poprzez wywołanie funkcji dispatch
zwróconej przez useReducer
.
Teraz przekaż data
i DragAndDrop
je jako props
do komponentu dispatch
, który masz w pliku App.js :
<DragAndDrop data={data} dispatch={dispatch} />
U góry komponentu DragAndDrop
możemy uzyskać dostęp do obu wartości z props
.
const { data, dispatch } = props;
Jeśli śledzisz repozytorium, odpowiednia gałąź to 03-define-reducers
.
Zakończmy logikę naszych programów obsługi zdarzeń. Zauważ, że wielokropek reprezentuje dwie linie:
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 }) };
Na poniższej ilustracji mamy zagnieżdżone strefy upuszczania A i B. A jest naszą strefą zainteresowania. W tym miejscu chcemy nasłuchiwać zdarzeń typu „przeciągnij i upuść”.
Podczas przeciągania do strefy upuszczania, za każdym razem, gdy trafiamy na granicę, uruchamiane jest zdarzenie ondragenter
. Dzieje się tak na granicach A-in
i B-in
. Ponieważ wchodzimy do strefy, zwiększamy dropDepth
.
Podobnie, podczas przeciągania ze strefy upuszczania, za każdym razem, gdy trafiamy na granicę, uruchamiane jest zdarzenie ondragleave
. Dzieje się tak na granicach A-out
i B-out
. Ponieważ opuszczamy strefę, zmniejszamy wartość dropDepth
. Zauważ, że nie ustawiamy inDropZone
na false
na granicy B-out
. Dlatego mamy tę linię do sprawdzenia dropDepth i powrotu z funkcji dropDepth
większej niż 0
.
if (data.dropDepth > 0) return
Dzieje się tak dlatego, że mimo uruchomienia zdarzenia ondragleave
nadal znajdujemy się w strefie A. Dopiero po tym, jak trafiliśmy A-out
, a dropDepth
wynosi teraz 0
, ustawiamy inDropZone
na false
. W tym momencie opuściliśmy wszystkie strefy zrzutu.
const handleDragOver = e => { ... e.dataTransfer.dropEffect = 'copy'; dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: true }); };
Za każdym razem, gdy to zdarzenie jest uruchamiane, ustawiamy inDropZone
na true
. To mówi nam, że jesteśmy w strefie zrzutu. Ustawiamy również dropEffect
w obiekcie dataTransfer
na copy
. Na komputerze Mac powoduje to wyświetlenie zielonego znaku plus podczas przeciągania elementu w strefie upuszczania.
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 }); } };
Możemy uzyskać dostęp do usuniętych plików za pomocą e.dataTransfer.files
. Wartość jest obiektem podobnym do tablicy, więc używamy składni rozprzestrzeniania tablicy, aby przekonwertować ją na tablicę JavaScript
.
Teraz musimy sprawdzić, czy istnieje co najmniej jeden plik, zanim spróbujemy dodać go do naszej tablicy plików. Dbamy również o to, aby nie uwzględniać plików, które już znajdują się na naszej fileList
. Obiekt dataTransfer
jest czyszczony w ramach przygotowań do następnej operacji przeciągania i upuszczania. Resetujemy również wartości dropDepth
i inDropZone
.
Zaktualizuj className
div
w komponencie DragAndDrop
. Spowoduje to warunkową zmianę nazwy data.inDropZone
div
w zależności od wartości className
.
<div className={data.inDropZone ? 'drag-drop-zone inside-drag-area' : 'drag-drop-zone'} ... > <p>Drag files here to upload</p> </div>
Wyrenderuj listę plików w App.js , mapując za pomocą 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>
Teraz spróbuj przeciągnąć i upuścić kilka plików do strefy upuszczania. Zobaczysz, że gdy wejdziemy w strefę zrzutu, tło staje się mniej nieprzejrzyste, ponieważ aktywowana jest klasa inside-drag-area
.
Po zwolnieniu plików w strefie upuszczania zobaczysz nazwy plików wymienione w strefie upuszczania:
Pełna wersja tego samouczka znajduje się w gałęzi 04-finish-handlers
.
Wniosek
Widzieliśmy, jak obsługiwać przesyłanie plików w React za pomocą interfejsu API przeciągania i upuszczania HTML
. Nauczyliśmy się również, jak zarządzać stanem za pomocą useReducer
. Moglibyśmy rozszerzyć funkcję file handleDrop
. Na przykład, jeśli chcielibyśmy, moglibyśmy dodać kolejną kontrolę, aby ograniczyć rozmiary plików. Może to nastąpić przed lub po sprawdzeniu istniejących plików. Moglibyśmy również sprawić, by strefa upuszczania była klikalna bez wpływu na funkcję przeciągania i upuszczania.
Zasoby
- „Odniesienie do Hooks API:
useReducer
”, React Docs - „Interfejs API przeciągania i upuszczania HTML”, dokumentacja internetowa MDN
- „Przykłady tworzenia stron WWW i XML przy użyciu DOM”, dokumentacja internetowa MDN
- „Jak zrobić program do przesyłania plików typu „przeciągnij i upuść” za pomocą waniliowego JavaScriptu”, Joseph Zimmerman, Smashing Magazine
- „Proste przesyłanie plików metodą „przeciągnij i upuść” w reakcji”, Egor Egorov, Medium