كيفية استخدام واجهة برمجة تطبيقات السحب والإفلات بتنسيق HTML في React

نشرت: 2022-03-10
ملخص سريع ↬ في هذا البرنامج التعليمي ، سننشئ مكون سحب وإفلات React لعمليات تحميل الملفات والصور. في هذه العملية ، سنتعرف على واجهة برمجة تطبيقات السحب والإفلات بتنسيق HTML. سنتعلم أيضًا كيفية استخدام الخطاف useReducer لإدارة الحالة في مكون وظيفي لـ React.

تعد واجهة برمجة تطبيقات السحب والإفلات واحدة من أروع ميزات HTML. يساعدنا في تنفيذ ميزات السحب والإفلات في متصفحات الويب.

في السياق الحالي ، سنقوم بسحب الملفات من خارج المتصفح. عند إسقاط الملف (الملفات) ، نضعها في قائمة ونعرض أسمائها. مع وجود الملفات في متناول اليد ، يمكننا بعد ذلك إجراء بعض العمليات الأخرى على الملف (الملفات) ، على سبيل المثال تحميلها على خادم سحابي.

في هذا البرنامج التعليمي ، سنركز على كيفية تنفيذ إجراء السحب والإفلات في تطبيق React. إذا كان ما تحتاجه هو تطبيق JavaScript عادي ، فربما ترغب أولاً في قراءة "كيفية إنشاء برنامج تحميل ملفات السحب والإفلات باستخدام Vanilla JavaScript" ، وهو برنامج تعليمي ممتاز كتبه جوزيف زيمرمان منذ وقت ليس ببعيد.

dragenter ، dragleave ، dragover ، drop الأحداث

هناك ثمانية أحداث سحب وإفلات مختلفة. كل واحد يتم إطلاقه في مرحلة مختلفة من عملية السحب والإفلات. في هذا البرنامج التعليمي ، سنركز على الأربعة التي يتم إطلاقها عند إسقاط عنصر ما في منطقة الإسقاط: dragenter ، dragleave ، dragover ، drop .

  1. يتم تشغيل الحدث dragenter عندما يدخل عنصر تم سحبه في هدف إسقاط صالح.
  2. يتم تشغيل الحدث dragleave عندما يترك عنصر تم سحبه هدف إسقاط صالح.
  3. يتم dragover حدث السحب عندما يتم سحب عنصر تم سحبه فوق هدف إسقاط صالح. (تنطلق كل بضع مئات من الألف من الثانية).
  4. يتم تشغيل حدث drop عندما يسقط عنصر على هدف إسقاط صالح ، أي يتم سحبه فوق وإطلاقه.

يمكننا تحويل أي عنصر HTML إلى هدف إسقاط صالح عن طريق تحديد سمات معالج الأحداث ondragover و ondrop .

يمكنك معرفة كل شيء عن الأحداث الثمانية من مستندات الويب MDN.

المزيد بعد القفز! أكمل القراءة أدناه ↓

أحداث السحب والإفلات في React

للبدء ، قم باستنساخ الريبو التعليمي من عنوان URL هذا:

 https://github.com/chidimo/react-dnd.git

تحقق من فرع 01-start Start. تأكد من تثبيت yarn لديك أيضًا. يمكنك الحصول عليه من yarnpkg.com.

ولكن إذا كنت تفضل ذلك ، فقم بإنشاء مشروع React جديد واستبدل محتوى App.js بالكود أدناه:

 import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <h1>React drag-and-drop component</h1> </div> ); } export default App;

أيضًا ، استبدل محتوى App.css بنمط 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; }

إذا قمت باستنساخ الريبو ، فقم بإصدار الأوامر التالية (بالترتيب) لبدء التطبيق:

 yarn # install dependencies yarn start # start the app

الخطوة التالية هي إنشاء مكون السحب والإفلات. قم بإنشاء ملف DragAndDrop.js داخل src/ المجلد. أدخل الوظيفة التالية داخل الملف:

 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;

في قسم div ، حددنا سمات معالج حدث HTML للتركيز. يمكنك أن ترى أن الاختلاف الوحيد عن HTML الخالص هو غلاف الجمل.

أصبح div الآن هدف إسقاط صالحًا نظرًا لأننا حددنا سمات معالج الحدث onDragOver و onDrop .

لقد حددنا أيضًا وظائف للتعامل مع تلك الأحداث. تستقبل كل دالة من وظائف المعالج كائن الحدث كوسيطة لها.

لكل من معالجات الأحداث ، نطلق على preventDefault() منع المتصفح من تنفيذ سلوكه الافتراضي. سلوك المتصفح الافتراضي هو فتح الملف الذي تم إسقاطه. نقوم أيضًا باستدعاء stopPropagation() للتأكد من أن الحدث لا يتم نشره من العناصر الفرعية إلى العناصر الأصل.

قم باستيراد مكون DragAndDrop إلى مكون App وعرضه أسفل العنوان.

 <div className="App"> <h1>React drag-and-drop component</h1> <DragAndDrop /> </div>

شاهد الآن المكون في المتصفح وسترى شيئًا مثل الصورة أدناه.

منطقة الإسقاط
div المراد تحويله إلى منطقة إسقاط (معاينة كبيرة)

إذا كنت تتابع الريبو ، فإن الفرع المقابل هو 02-start-dragndrop

إدارة الدولة باستخدام useReducer

ستكون خطوتنا التالية هي كتابة المنطق لكل من معالجات الأحداث لدينا. قبل أن نفعل ذلك ، علينا أن نفكر في الكيفية التي ننوي بها تتبع الملفات التي تم إسقاطها. هذا هو المكان الذي نبدأ فيه التفكير في إدارة الدولة.

سنتعقب الحالات التالية أثناء عملية السحب والإفلات:

  1. dropDepth
    سيكون هذا عددًا صحيحًا. سنستخدمه لتتبع عدد المستويات الموجودة في عمق منطقة الإسقاط. في وقت لاحق ، سأشرح هذا مع توضيح. ( قروض لإيجور إيجوروف لإلقاء الضوء على هذا من أجلي! )
  2. inDropZone
    سيكون هذا منطقيًا. سنستخدم هذا لتتبع ما إذا كنا داخل منطقة الإسقاط أم لا.
  3. FileList
    ستكون هذه قائمة. سنستخدمه لتتبع الملفات التي تم إسقاطها في منطقة الإسقاط.

للتعامل مع الحالات ، توفر useState الخطاف useState و useReducer . سنختار الخطاف useReducer نظرًا لأننا سنتعامل مع المواقف التي تعتمد فيها الحالة على الحالة السابقة.

يقبل الخطاف useReducer من النوع (state, action) => newState ، ويعيد الحالة الحالية المقترنة بطريقة dispatch .

يمكنك قراءة المزيد عن useReducer في مستندات React.

داخل مكون App (قبل بيان return ) ، أضف الكود التالي:

 ... 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: [] } ) ...

يقبل الخطاف useReducer : مخفض وحالة أولية. تقوم بإرجاع الحالة الحالية ووظيفة dispatch التي يتم من خلالها تحديث الحالة. يتم تحديث الحالة عن طريق إرسال إجراء يحتوي على type وحمولة اختيارية. يعتمد التحديث الذي تم إجراؤه على حالة المكون على ما يتم إرجاعه من بيان الحالة كنتيجة لنوع الإجراء. (لاحظ هنا أن حالتنا الأولية هي object .)

لكل من متغيرات الحالة ، قمنا بتعريف بيان الحالة المقابل لتحديثه. يتم إجراء التحديث من خلال استدعاء وظيفة dispatch التي يتم إرجاعها بواسطة useReducer .

الآن قم بتمرير data dispatch DragAndDrop props ملف App.js الخاص بك:

 <DragAndDrop data={data} dispatch={dispatch} />

في الجزء العلوي من مكون DragAndDrop ، يمكننا الوصول إلى كلتا القيمتين من props .

 const { data, dispatch } = props;

إذا كنت تتابع الريبو ، فإن الفرع المقابل هو 03-define-reducers .

لننتهي من منطق معالجات الأحداث. لاحظ أن علامة القطع تمثل السطرين:

 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 }) };

في الرسم التوضيحي التالي ، لدينا مناطق إسقاط متداخلة A و B. A هي منطقة اهتمامنا. هذا هو المكان الذي نريد الاستماع إلى أحداث السحب والإفلات.

رسم توضيحي لأحداث ondragenter و ondragleave
رسم توضيحي لأحداث ondragenter و ondragleave (معاينة كبيرة)

عند السحب إلى منطقة الإسقاط ، في كل مرة نصل فيها إلى حد ، يتم إطلاق حدث ondragenter . يحدث هذا عند الحدود A-in و B-in . نظرًا لأننا ندخل المنطقة ، dropDepth .

وبالمثل ، عند السحب من منطقة الإسقاط ، في كل مرة نصل فيها إلى حد ، يتم إطلاق حدث ondragleave . يحدث هذا عند الحدود A-out و B-out . نظرًا لأننا نغادر المنطقة ، فإننا نقلل من قيمة dropDepth . لاحظ أننا لم نقم بتعيين inDropZone على false عند الحد B-out . لهذا السبب لدينا هذا الخط للتحقق من عمق الإسقاط والعودة من وظيفة dropDepth أكبر من 0 .

 if (data.dropDepth > 0) return

هذا لأنه على الرغم من إطلاق حدث ondragleave ، إلا أننا ما زلنا داخل المنطقة A. فقط بعد أن نصل إلى A-out ، وأصبح dropDepth الآن 0 ، قمنا بتعيين inDropZone على false . في هذه المرحلة ، تركنا جميع مناطق الإسقاط.

 const handleDragOver = e => { ... e.dataTransfer.dropEffect = 'copy'; dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: true }); };

في كل مرة يتم فيها إطلاق هذا الحدث ، نقوم بتعيين inDropZone على " true ". يخبرنا هذا أننا داخل منطقة الإسقاط. قمنا أيضًا بتعيين dropEffect على كائن dataTransfer copy . على جهاز Mac ، يكون لهذا تأثير إظهار علامة الجمع الخضراء أثناء قيامك بسحب عنصر في منطقة الإسقاط.

 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 }); } };

يمكننا الوصول إلى الملفات التي تم إسقاطها باستخدام e.dataTransfer.files . القيمة عبارة عن كائن يشبه المصفوفة ، لذلك نستخدم صيغة انتشار المصفوفة لتحويلها إلى مصفوفة JavaScript .

نحتاج الآن إلى التحقق مما إذا كان هناك ملف واحد على الأقل قبل محاولة إضافته إلى مجموعة الملفات الخاصة بنا. نتأكد أيضًا من عدم تضمين الملفات الموجودة بالفعل في قائمة الملفات الخاصة fileList . يتم مسح كائن dataTransfer استعدادًا لعملية السحب والإفلات التالية. نقوم أيضًا بإعادة تعيين قيم dropDepth و inDropZone .

قم بتحديث className الخاص بـ div في مكون DragAndDrop . سيؤدي هذا إلى تغيير اسم div className لقيمة data.inDropZone .

 <div className={data.inDropZone ? 'drag-drop-zone inside-drag-area' : 'drag-drop-zone'} ... > <p>Drag files here to upload</p> </div>

اعرض قائمة الملفات في App.js عن طريق التعيين عبر 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>

حاول الآن سحب بعض الملفات وإفلاتها في منطقة الإسقاط. ستلاحظ أنه عند دخولنا منطقة الإسقاط ، تصبح الخلفية أقل تعتيمًا نظرًا لتنشيط فئة inside-drag-area .

عند تحرير الملفات داخل منطقة الإسقاط ، سترى أسماء الملفات مدرجة ضمن منطقة الإسقاط:

تظهر منطقة الإسقاط عتامة منخفضة أثناء السحب
تظهر منطقة الإسقاط عتامة منخفضة أثناء السحب (معاينة كبيرة)
قائمة الملفات التي تم إسقاطها في منطقة الإسقاط
قائمة بالملفات التي تم إسقاطها في منطقة الإسقاط (معاينة كبيرة)

النسخة الكاملة من هذا البرنامج التعليمي موجودة في فرع 04-finish-handlers .

خاتمة

لقد رأينا كيفية التعامل مع تحميلات الملفات في React باستخدام واجهة برمجة تطبيقات السحب والإفلات بتنسيق HTML . لقد تعلمنا أيضًا كيفية إدارة الحالة باستخدام الخطاف useReducer . يمكننا تمديد وظيفة handleDrop الملف. على سبيل المثال ، يمكننا إضافة فحص آخر لتحديد أحجام الملفات إذا أردنا ذلك. يمكن أن يأتي هذا قبل أو بعد التحقق من الملفات الموجودة. يمكننا أيضًا جعل منطقة الإسقاط قابلة للنقر دون التأثير على وظيفة السحب والإفلات.

موارد

  • "مرجع واجهة برمجة تطبيقات Hooks: useReducer " ، React Docs
  • "HTML Drag-and-Drop API" ، مستندات الويب MDN
  • "أمثلة على تطوير الويب و XML باستخدام DOM ،" مستندات الويب MDN
  • "كيفية إنشاء برنامج تحميل ملفات السحب والإفلات باستخدام Vanilla JavaScript ،" جوزيف زيمرمان ، مجلة Smashing
  • "تحميل ملف سحب وإفلات بسيط في رد فعل" ، إيجور إيجوروف ، متوسط