Comment utiliser l'API HTML Drag-And-Drop dans React
Publié: 2022-03-10L'API glisser-déposer est l'une des fonctionnalités les plus intéressantes de HTML. Il nous aide à implémenter des fonctionnalités de glisser-déposer dans les navigateurs Web.
Dans le contexte actuel, nous ferons glisser des fichiers depuis l'extérieur du navigateur. En déposant le ou les fichiers, nous les mettons sur une liste et affichons leurs noms. Avec les fichiers en main, nous pourrions alors effectuer une autre opération sur le ou les fichiers, par exemple les télécharger sur un serveur cloud.
Dans ce didacticiel, nous nous concentrerons sur la manière d'implémenter l'action de glisser-déposer dans une application React. Si vous avez besoin d'une implémentation JavaScript
simple, vous voudrez peut-être d'abord lire "Comment créer un téléchargeur de fichiers par glisser-déposer avec Vanilla JavaScript", un excellent didacticiel écrit par Joseph Zimmerman il n'y a pas si longtemps.
Les dragenter
, dragleave
, dragover
et drop
Il existe huit événements de glisser-déposer différents. Chacun se déclenche à une étape différente de l'opération de glisser-déposer. Dans ce didacticiel, nous allons nous concentrer sur les quatre qui sont déclenchés lorsqu'un élément est déposé dans une zone de dépôt : dragenter
, dragleave
, dragover
et drop
.
- L'événement
dragenter
se déclenche lorsqu'un élément déplacé entre dans une cible de dépôt valide. - L'événement
dragleave
se déclenche lorsqu'un élément déplacé quitte une cible de dépôt valide. - L'événement
dragover
se déclenche lorsqu'un élément déplacé est déplacé sur une cible de dépôt valide. (Il se déclenche toutes les quelques centaines de millisecondes.) - L'événement
drop
se déclenche lorsqu'un élément tombe sur une cible de dépôt valide, c'est-à-dire glissé et relâché.
Nous pouvons transformer n'importe quel élément HTML en une cible de dépôt valide en définissant les attributs de gestionnaire d'événements ondragover
et ondrop
.
Vous pouvez tout savoir sur les huit événements à partir des documents Web MDN.
Glisser-déposer des événements dans React
Pour commencer, clonez le dépôt du didacticiel à partir de cette URL :
https://github.com/chidimo/react-dnd.git
Découvrez la branche 01-start
. Assurez-vous que le yarn
est également installé. Vous pouvez l'obtenir sur yarnpkg.com.
Mais si vous préférez, créez un nouveau projet React et remplacez le contenu de App.js par le code ci-dessous :
import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <h1>React drag-and-drop component</h1> </div> ); } export default App;
Remplacez également le contenu de App.css par le style CSS ci-dessous :
.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; }
Si vous avez cloné le référentiel, émettez les commandes suivantes (dans l'ordre) pour démarrer l'application :
yarn # install dependencies yarn start # start the app
L'étape suivante consiste à créer un composant glisser-déposer. Créez un fichier DragAndDrop.js dans le dossier src/
. Entrez la fonction suivante dans le fichier :
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;
Dans le return div
, nous avons défini nos attributs de gestionnaire d'événements HTML
focus. Vous pouvez voir que la seule différence avec le HTML
pur est la casse camel.
La div
est maintenant une cible de dépôt valide puisque nous avons défini les attributs de gestionnaire d'événements onDragOver
et onDrop
.
Nous avons également défini des fonctions pour gérer ces événements. Chacune de ces fonctions de gestionnaire reçoit l'objet événement comme argument.
Pour chacun des gestionnaires d'événements, nous appelons preventDefault()
pour empêcher le navigateur d'exécuter son comportement par défaut. Le comportement par défaut du navigateur consiste à ouvrir le fichier déposé. Nous appelons également stopPropagation()
pour nous assurer que l'événement n'est pas propagé des éléments enfants aux éléments parents.
Importez le composant DragAndDrop
dans le composant App
et affichez-le sous l'en-tête.
<div className="App"> <h1>React drag-and-drop component</h1> <DragAndDrop /> </div>
Affichez maintenant le composant dans le navigateur et vous devriez voir quelque chose comme l'image ci-dessous.

div
à convertir en zone de dépôt ( Grand aperçu ) Si vous suivez avec le repo, la branche correspondante est 02-start-dragndrop
Gestion de l'état avec le crochet useReducer
Notre prochaine étape consistera à écrire la logique de chacun de nos gestionnaires d'événements. Avant de faire cela, nous devons considérer comment nous avons l'intention de garder une trace des fichiers supprimés. C'est là que nous commençons à penser à la gestion de l'État.
Nous garderons une trace des états suivants pendant l'opération de glisser-déposer :
-
dropDepth
Ce sera un entier. Nous l'utiliserons pour garder une trace du nombre de niveaux de profondeur dans la zone de largage. Plus tard, j'expliquerai cela avec une illustration. ( Merci à Egor Egorov d'avoir mis en lumière celui-ci pour moi ! ) -
inDropZone
Ce sera un booléen. Nous l'utiliserons pour savoir si nous sommes à l'intérieur de la zone de largage ou non. -
FileList
Ce sera une liste. Nous l'utiliserons pour garder une trace des fichiers qui ont été déposés dans la zone de dépôt.
Pour gérer les états, React fournit les crochets useState
et useReducer
. Nous opterons pour le hook useReducer
étant donné que nous aurons affaire à des situations où un état dépend de l'état précédent.
Le hook useReducer
accepte un réducteur de type (state, action) => newState
et renvoie l'état actuel associé à une méthode de dispatch
.
Vous pouvez en savoir plus sur useReducer
dans les documents React.
Dans le composant App
(avant l'instruction return
), ajoutez le code suivant :

... 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: [] } ) ...
Le crochet useReducer
accepte deux arguments : un réducteur et un état initial. Il renvoie l'état actuel et une fonction de dispatch
avec laquelle mettre à jour l'état. L'état est mis à jour en envoyant une action qui contient un type
et une charge utile facultative. La mise à jour apportée à l'état du composant dépend de ce qui est renvoyé par l'instruction case à la suite du type d'action. (Notez ici que notre état initial est un object
.)
Pour chacune des variables d'état, nous avons défini une instruction case correspondante pour la mettre à jour. La mise à jour est effectuée en appelant la fonction dispatch
renvoyée par useReducer
.
Transmettez maintenant les data
et envoyez-les en DragAndDrop
dispatch
vous avez dans votre fichier props
:
<DragAndDrop data={data} dispatch={dispatch} />
En haut du composant DragAndDrop
, nous pouvons accéder aux deux valeurs à partir de props
.
const { data, dispatch } = props;
Si vous suivez avec le repo, la branche correspondante est 03-define-reducers
.
Terminons la logique de nos gestionnaires d'événements. Notez que les points de suspension représentent les deux lignes :
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 }) };
Dans l'illustration qui suit, nous avons imbriqué les drop zones A et B. A est notre zone d'intérêt. C'est là que nous voulons écouter les événements de glisser-déposer.

ondragenter
et ondragleave
( Grand aperçu ) Lorsque vous glissez dans une zone de dépôt, chaque fois que nous atteignons une limite, l'événement ondragenter
est déclenché. Cela se produit aux limites A-in
et B-in
. Puisque nous entrons dans la zone, nous incrémentons dropDepth
.
De même, lorsque nous sortons d'une zone de dépôt, chaque fois que nous atteignons une limite, l'événement ondragleave
est déclenché. Cela se produit aux limites A-out
et B-out
. Puisque nous quittons la zone, nous décrémentons la valeur de dropDepth
. Notez que nous ne définissons pas inDropZone
sur false
à la limite B-out
. C'est pourquoi nous avons cette ligne pour vérifier le dropDepth et revenir de la fonction dropDepth
supérieur à 0
.
if (data.dropDepth > 0) return
En effet, même si l'événement ondragleave
est déclenché, nous sommes toujours dans la zone A. Ce n'est qu'après avoir frappé A-out
et dropDepth
vaut maintenant 0
que nous avons défini inDropZone
sur false
. À ce stade, nous avons quitté toutes les zones de largage.
const handleDragOver = e => { ... e.dataTransfer.dropEffect = 'copy'; dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: true }); };
Chaque fois que cet événement se déclenche, nous définissons inDropZone
sur true
. Cela nous indique que nous sommes à l'intérieur de la zone de largage. Nous définissons également le dropEffect
sur l'objet dataTransfer
pour copy
. Sur un Mac, cela a pour effet d'afficher un signe plus vert lorsque vous faites glisser un élément dans la zone de dépôt.
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 }); } };
Nous pouvons accéder aux fichiers déposés avec e.dataTransfer.files
. La valeur est un objet de type tableau, nous utilisons donc la syntaxe de propagation de tableau pour la convertir en tableau JavaScript
.
Nous devons maintenant vérifier s'il existe au moins un fichier avant d'essayer de l'ajouter à notre tableau de fichiers. Nous nous assurons également de ne pas inclure les fichiers qui sont déjà sur notre fileList
. L'objet dataTransfer
est effacé en vue de la prochaine opération de glisser-déposer. Nous réinitialisons également les valeurs de dropDepth
et inDropZone
.
Mettez à jour le className
de la div
dans le composant DragAndDrop
. Cela modifiera conditionnellement le className
de la div
en fonction de la valeur de data.inDropZone
.
<div className={data.inDropZone ? 'drag-drop-zone inside-drag-area' : 'drag-drop-zone'} ... > <p>Drag files here to upload</p> </div>
Affichez la liste des fichiers dans App.js en mappant via 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>
Essayez maintenant de faire glisser et de déposer des fichiers dans la zone de dépôt. Vous verrez qu'à mesure que nous entrons dans la zone de dépôt, l'arrière-plan devient moins opaque car la classe inside-drag-area
est activée.
Lorsque vous publiez les fichiers à l'intérieur de la zone de dépôt, vous verrez les noms de fichiers répertoriés sous la zone de dépôt :


La version complète de ce tutoriel se trouve sur la branche 04-finish-handlers
.
Conclusion
Nous avons vu comment gérer les téléchargements de fichiers dans React à l'aide de l'API HTML
glisser-déposer. Nous avons également appris à gérer l'état avec le crochet useReducer
. Nous pourrions étendre la fonction de fichier handleDrop
. Par exemple, nous pourrions ajouter une autre vérification pour limiter la taille des fichiers si nous le voulions. Cela peut venir avant ou après la vérification des fichiers existants. Nous pourrions également rendre la zone de dépôt cliquable sans affecter la fonctionnalité de glisser-déposer.
Ressources
- « Référence de l'API Hooks :
useReducer
», React Docs - « API de glisser-déposer HTML », documentation Web MDN
- "Exemples de développement Web et XML à l'aide du DOM", documentation Web MDN
- "Comment créer un téléchargeur de fichiers par glisser-déposer avec Vanilla JavaScript", Joseph Zimmerman, Smashing Magazine
- "Téléchargement simple de fichiers par glisser-déposer dans React", Egor Egorov, Medium