Comment utiliser l'API HTML Drag-And-Drop dans React

Publié: 2022-03-10
Résumé rapide ↬ Dans ce didacticiel, nous allons créer un composant glisser-déposer React pour les téléchargements de fichiers et d'images. Au cours du processus, nous découvrirons l'API de glisser-déposer HTML. Nous apprendrons également à utiliser le crochet useReducer pour gérer l'état dans un composant fonctionnel React.

L'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 .

  1. L'événement dragenter se déclenche lorsqu'un élément déplacé entre dans une cible de dépôt valide.
  2. L'événement dragleave se déclenche lorsqu'un élément déplacé quitte une cible de dépôt valide.
  3. 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.)
  4. 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.

Plus après saut! Continuez à lire ci-dessous ↓

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.

Zone de largage
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 :

  1. 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 ! )
  2. inDropZone
    Ce sera un booléen. Nous l'utiliserons pour savoir si nous sommes à l'intérieur de la zone de largage ou non.
  3. 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.

Une illustration des événements ondragenter et ondragleave
Une illustration des événements 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 :

Zone de dépôt montrant une faible opacité pendant le dragover
Zone de dépôt montrant une faible opacité pendant le dragover ( Grand aperçu )
Une liste des fichiers déposés dans la zone de dépôt
Une liste des fichiers déposés dans la zone de dépôt ( Grand aperçu )

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