چگونه از API کشیدن و رها کردن یا Drag-And-Drop در React استفاده کنیم

ترجمه و تالیف : ژینو عباسی
تاریخ انتشار : 13 اردیبهشت 99
خواندن در 5 دقیقه
دسته بندی ها : react

API کشیدن و رها کردن(drag & drop) یکی از جالب‌ترین ویژگی‌های HTML است. این ویژگی به ما کمک می‌کند ویژگی کشیدن و رها کردن را در مرورگر وب پیاده سازیم.

در این محتوا، ما فایل‌هایی را خارج از مرورگر می‌کشیم. در قسمت رها کردن فایل‌ها، ما آن‌ها را در یک لیست قرار داده و اسامی آن‌ها را نمایش می‌دهیم. با در دست داشتن این فایل‌ها در دست، ما سپس می‌توانیم بعدتر چندین عملیات دیگر را بر روی فایل‌ها اجرا کنیم؛ به عنوان مثال آن‌ها را بر روی یک سرور ابری آپلود کنیم.

در این آموزش، ما بر روی چگونگی پیاده‌سازی اقدامات کشیدن و رها کردن در یک برنامه React تمرکز خواهیم کرد. اگر شما به یک پیاده‌سازی ساده جاوااسکریپت نیاز دارید، شاید بهتر باشد در ابتدا درمورد "چگونگی کشیدن و رها کردن آپلود کننده فایل با وانیلا جاوااسکریپت یا Vanilla JavaScript"، مقاله‌ای که توسط Joseph Zimmerman چندی پیش نوشته شد، را بخوانید.

رویدادهای dragenter، dragleave، dragover و drop

هشت رویداد مختلف کشیدن و رها کردن وجود دارد. هر کدام از آن‌ها در مرحله مختلفی از عملیات کشیدن و رها کردن فعال می‌شوند. در این آموزش ما بر روی چهار تا از آن‌ها که در زمان رها کردن یک آیتم در محل رها کردن، رها میشود، تمرکز خواهیم کرد : dragenter، dragleave، dragover و drop.

  1. رویداد dragenter زمانی فعال می‌شود که آیتمی که کشیده شده است وارد یک محدوده رها کردن معتبر می‌گردد.
  2. رویداد  dragleave زمانی فعال می‌شود که آیتمی که كشیده شده است از محدوده رها کردن معتبر خارج می‌شود. 
  3. رویداد  dragover زمانی فعال می‌شود که آیتمی که کشیده شده است بر روی محدوده رها کردن معتبر کشیده می‌شود.(هر چند صد میلی ثانیه فعال می‌شود)
  4. رویداد drop زمانی فعال می‌شود که يک آیتم بر روی محدوده معتبر رها کردن، رها می‌شود؛ به عنوان مثال بر روی آن کشیده و رها شود.

ما می‌توانیم هر عامل HTML را به یک هدف و محدوده معتبر رها کردن با تعریف کردن ویژگی‌های نگه‌دارنده رویدادهای onedragover و ondrop تبدیل کنیم.

رویداد کشیدن و رها کردن در React

برای شروع، مخزن یا repo آموزش را از این URL کپی کنید:

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

شاخه ۰۱-start را ببینید. مطمئن شوید شما yarn را نیز نصب کرده‌اید. شما می‌توانید آن را از yarnpkg.com دریافت کنید.

اما اگر شما ترجیح می‌دهید، یک پروژه React جدید بسازید و محتوای App.js را با کد زیر جایگزین کنید:

import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      <h۱>React drag-and-drop component</h۱>
    </div>
  );
}
export default App;

همچنین، محتوای App.css را با شیوه CSS زیر جایگزین کنید:

.App {
  margin: ۲rem;
  text-align: center;
}
h۱ {
  color: #۰۷F;
}
.drag-drop-zone {
  padding: ۲rem;
  text-align: center;
  background: #۰۷F;
  border-radius: ۰.۵rem;
  box-shadow: ۵px ۵px ۱۰px #C۰C۰C۰;
}
.drag-drop-zone p {
  color: #FFF;
}
.drag-drop-zone.inside-drag-area {
  opacity: ۰.۷;
}
.dropped-files li {
  color: #۰۷F;
  padding: ۳px;
  text-align: left;
  font-weight: bold;
}

اگر شما repo را کپی کردید، دستورهای زیر را نیز اجرا کنید تا برنامه را آغاز کنید:

yarn # install dependencies
yarn start # start the app

قدم بعدی این است که یک مؤلفه کشیدن و رها کردن بسازیم. یک فایل DragAndDrop.js داخل پوشه scr/ بسازید. تابع زیر را درون فایل وارد کنید:

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 ساده در نگارش شتری یا camel-casing است.

اکنون div یک محدوده رها کردن معتبر است چراکه ما ویژگی‌های نگه‌دارنده onDragOver وonDrop را تعریف کرده‌ایم.

ما همچنین تابع‌ها را برای نگه داشتن این رویدادها تعریف کرده‌ایم. هر کدام از این تابع‌های نگه‌دارنده موضوع رویداد را به عنوان برهان خود دریافت می كنند.

برای هر کدام از این نگه‌دارنده‌های رویداد، ما از یک preventDefault( ) برای بازداری مرورگر از اجرای رفتار پیش‌فرض خود استفاده می‌کنیم. رفتار پیش‌فرض مرورگر، باز کردن فایل کشیده شده است. ما همچنین از stopProgration( ) برای اطمینان حاصل کردن از اینکه رویداد از عناصر فرزند (child elemnt) به عناصر والد (parent) گسترش نیافته‌اند.

مؤلفه DragAndDrop را به درون مؤلفه App منتقل کنید و تحت عنوان زیر آن را رندر کنید.

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

اکنون مؤلفه را در مرورگر نمایش دهید و شما باید چیزی شبیه تصویر زیر ببینید.

چگونه از API کشیدن و رها کردن یا Drag-And-Drop در React استفاده کنیمچگونه از API کشیدن و رها کردن یا Drag-And-Drop در React استفاده کنیم

Div که به یک محدوده رها کردن تبدیل شده است.(نمایش بزرگ)

اگر طبق مخزن یا repo پیش بروید، شاخه پاسخگو ۰۲-start-dragndrop خواهد بود.

مدیریت گزاره‌ها یا state با هوک  (Hook)

قدم بعدی ما نوشتن منطق برای هر کدام از نگه‌دارنده رویدادهایمان است. قبل از اینکه این کار را انجام دهیم، ما باید شیوه‌ای که به کمک آن قصد داریم فایل‌های کشیده شده را ردگیری کنیم را در نظر بگیریم. اینجا است که ما به فکر مدیریت گزاره می‌افتیم.

ما stateهای زیر را طی عملیات کشیدن و رها کردن ردگیری می‌کنیم:

۱. dropDepth

این یک integer خواهد بود. ما از آن برای ردگیری اینکه تا چه سطح در محدوده رها کردن رفته‌ایم، استفاده می‌كنیم.

۲. inDropZone 

این یک Boolean خواهد بود. ما از این برای ردگیری اینکه آیا داخل محدوده رها کردن هستیم یا خیر استفاده می‌کنیم.

۳. FileList

این یک لیست خواهد بود. ما از آن برای ردگیری فایل‌هایی که در قسمت رها کردن، رها شده‌اند استفاده می‌کنیم.

برای مدیریت state ،React قلاب‌های useState و useReducer را ارائه می‌دهد. ما useReducer را انتخاب می‌کنیم که از آن در هنگام موقعیتی که یک گزاره به گزاره پیشین خود متکی است، استفاده می‌کنیم.

قلاب useReducer یک کاهش‌دهنده از نوع state, action) => newState) قبول می‌کند و گزاره فعلی را به همراه یک شیوه dispatch بازمی‌گرداند.

شما می‌توانید در مورد useReducer در React docs بیشتر مطالعه کنید.

داخل مؤلفه 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: ۰, inDropZone: false, fileList: [] }
)
...

قلاب useReducer دو برهان قبول می‌کند: یک کاهنده و یک گزاره اولیه. این قلاب گزاره فعلی را به همراه یک تابع dispatch که به کمک آن گزاره را به‌روزرسانی می‌کند، بازمی‌گرداند. گزاره با گسیل دادن اقدامی که شامل یک type و یک پی‌لود اختیاری است، به‌روزرسانی می‌شود. به‌روزسانی صورت گرفته بر روی گزاره مؤلفه، به چیزی که از گزاره اصلی به عنوان نتیجه نوع اقدام بازگردانده می‌شود، وابسته است.

برای هر کدام از متغیرهای گزاره، ما یک گزاره اصلی پاسخگو برای به‌روزرسانی آن تعریف کردیم. به‌روزرسانی با استناد به تابع dipatch که توسط useReducer بازگردانده شده است، اجرا می‌شود.

اکنون data و dispatch را به عنوان props به مؤلفه DragAndDrop که در فایل App.js خود دارید، منتقل کنید:

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

بالای مؤلفه DragAndDrop، ما می‌توانیم به هر دو مقدار از  props دسترسی پیدا کنیم.

const { data, dispatch } = props;

اگر شما بر اساس repo پیش بروید، شاخه پاسخگو  ۰۳-define-reducersخواهد بود.

بیایید منطق نگه‌دارنده رویدادمان را تمام کنیم. به یاد داشته باشید که خطوط جا انداخته شده، بیانگر دو خط است:

e.preventDefault()
e.stopPropagation()


const handleDragEnter = e => {
  ...
  dispatch({ type: 'SET_DROP_DEPTH', dropDepth: data.dropDepth + ۱ });
};

const handleDragLeave = e => {
  ...
  dispatch({ type: 'SET_DROP_DEPTH', dropDepth: data.dropDepth - ۱ });
  if (data.dropDepth > ۰) return
  dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: false })
};

در تصویر زیر، ما دو محدوده رها کردن پیچیده A و B را داریم. A محدوده‌ی مورد نظر ما است. اینجا است که ما رویدادهای کشیدن و رها کردن خود را لیست کنیم.

چگونه از API کشیدن و رها کردن یا Drag-And-Drop در React استفاده کنیمچگونه از API کشیدن و رها کردن یا Drag-And-Drop در React استفاده کنیم

تصویری از رویدادهای ondragenter و ondragleave (نمایش بزرگ)

هنگام کشیدن به داخل یک محدوده رها کردن، هر بار که ما از یک محدوده و مرز عبور می‌کنیم، رویداد ondragleave فعال می‌شود. این اتفاق در مرزهای A-in و B-in رخ می‌دهد. از آنجایی که ما وارد محدوده می‌شویم، ما dropDepth را افزایش می‌دهیم.

به همین ترتیب، هنگامی که از یک محدوه رها کردن نیز خارج می‌شویم، هربار که به یک مرز برخورد کنیم، رویداد ondragleave فعال می‌شود. این اتفاق در مرز های A-out و B-out رخ می‌دهد. از آنجایی که ما درحال ترک محدوده هستیم، ما مقدار dropDepth را کاهش می‌دهیم. دقت کنید که ماinDropZone را به false در مرز B-out تنظیم نمی‌کنیم. برای همین است که ما این خط را برای بررسی dropDepth و بازگشت از تابع dropDepth بیشتر از ۰ داریم.

if (data.dropDepth > ۰) return

این خط برای این است که اگرچه رویداد ondragleave فعال شده است، ما هنوز داخل محدوده A هستیم. تنها زمانی که ما به A-out می‌رسیم، و dropDepth اکنون ۰ است، می‌توانیم 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 > ۰) {
    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: ۰ });
    dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: false });
  }
};

ما به فایل‌های رها شده به کمک e.dataTransfer.files دسترسی پیدا خواهیم کرد. مقدار، یک موضوع آرایه مانند است، تا ما از آرایه برای پخش کردن نحوه و ترکیب برای تبدیل آن به یک آرایه جاوااسکریپت استفاده کنیم.

ما اکنون باید بررسی کنیم که آیا حداقل یک فایل قبل از اینکه قصد کنیم آن‌را به آرایه فایل‌هایمان اضافه کنیم، وجود دارد. ما همچنین باید اطمینان حاصل کنیم که فایل هایی که از قبل در filelist ما وجود دارد را دوباره وارد نکنیم. موضوع dataTransfer برای قدم بعدی عملیات کشیدن و رها کردن روشن‌سازی شده است. ما همچنین مقادیرdropDepth و inDropZone را ریست می‌کنیم.

classname در div را در مؤلفه DragAndDrop به‌روزرسانی کنید. این کار به صورت شرطی، classname در  div را بسته به مقدار 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">
  <h۱>React drag-and-drop component</h۱>
  <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 فعال شده است.

زمانی‌که شما فایل‌ها را داخل محدوده رها کردن، آزاد می‌کنید، شما اسامی فایل‌ها را که در زیر محدوده رها کردن لیست شده‌اند، خواهید دید:

چگونه از API کشیدن و رها کردن یا Drag-And-Drop در React استفاده کنیمچگونه از API کشیدن و رها کردن یا Drag-And-Drop در React استفاده کنیم

محدوده رها کردن که هنگام کشیدن تصویر بر روی آن، وضوحش کاسته می‌شود.(نمایش بزرگ)

چگونه از API کشیدن و رها کردن یا Drag-And-Drop در React استفاده کنیمچگونه از API کشیدن و رها کردن یا Drag-And-Drop در React استفاده کنیم

لیستی از فایل‌هایی که در محدوده رها کردن، رها شده اند.(نمایش بزرگ)

نسخه نهایی این راهنما بر روی شاخه ۰۴-finish-handlers قرار دارد.

نتیجه‌گیری

ما نحوه کنترل آپلود فایل در React با استفاده از API رها کردن و کشیدن HTML را دیدیم. ما همچنین یاد گرفتیم چگونه گزاره‌ state را با کمک قلاب useReducer مدیریت کنیم. توانستیم تابع handleDrop فایل را گسترش دهیم. به‌عنوان مثال در صورت تمایل، یک بررسی دیگر برای اعمال محدودیت سایز اضافه کنیم. این کار را می‌توانیم بعد یا قبل از بررسی فایل‌هایی که از قبل وجود دارند، انجام دهیم. ما همچنین توانستیم بدون تاثیرگذاشتن برروی تابع کشیدن و رها کردن، محدوده رها کردن را به نحوی بسازیم که بتوان بر روی آن کلیک کرد.

منبع