کتابخانه Beautiful-dnd در React — راهنمای مقدماتی

۱۱۳ بازدید
آخرین به‌روزرسانی: ۲۰ شهریور ۱۴۰۲
زمان مطالعه: ۵ دقیقه
کتابخانه Beautiful-dnd در React — راهنمای مقدماتی

در این نوشته به بررسی کتابخانه react-beautiful-dnd می‌پردازیم و از آن برای ایجاد یک بازی ویدئویی ساده برای کودکان با استفاده از CSS ،React و کمی تخیل استفاده می‌کنیم. در طول این مطلب فرض ما بر این است که شما با ری‌اکت آشنایی دارید. اگر چنین نیست می‌توانید از «مجموعه مطالب ری‌اکت فرادرس» برای کسب اطلاعات بیشتر کمک بگیرید.

در نوشتاری که در ادامه آمده است از ارائه تصاویری از مفاهیم کلی مانند تنظیمات و رابط کاربری اجتناب کرده‌ایم و صرفاً کتابخانه «درگ اند دراپ» ارائه شده است.

react-beautiful-dnd کتابخانه‌ای است که از سوی Atlassian ارائه شده است. این کتابخانه وظیفه ارائه تجربه کشیدن و رها کردن به صورت طبیعی را بر عهده دارد. این یک لایه تجرید بالاتر است که برای فهرست‌های افقی و عمودی ایجاد شده است. هدف ما ایجاد بازی زیر است:

کار خود را با مفاهیم اساسی آغاز می‌کنیم.

DragDropContext

محلی است که عمل کشیدن و رها کردن در آن اتفاق می‌افتد. این کامپوننت زمانی که آیتم قابل کشیدن رها می‌شود، onDragEnd را فراخوانی می‌کند و تنها تابع مورد نیاز است. همچنین می‌توان onDragStart و onDragUpdate را طوری تعریف کرد که به ترتیب وقتی آیتم کشیده می‌شود و همچنین وقتی در حین کشیده شدن آیتم اتفاقی می‌افتد فعال شوند.

Droppable

این کامپوننت جایی است که عناصر قابل کشیده شدن از آنجا برداشته شده و همچنین در آنجا رها می‌شوند. هر کامپوننت Droppable به برخی خصوصیات نیاز دارد که در ادامه توضیح خواهیم داد.

Draggable

آیتمی که باید جابجا شود را شامل می‌شود. مانند Droppable باید برخی خصوصیات برای این آیتم تعریف شود.

پس از معرفی مفاهیم اصلی در ادامه داده‌های اولیه‌ای که برای ایجاد بازی نیاز داریم را تعریف می‌کنیم:

const initialData = {
  column: {
    id: 'column-1',
      numberIds: ['four', 'one', 'five', 'three', 'two'],
    },
    numbers: {
      'five': { id: 'five', content: '5' },
      'four': { id: 'four', content: '4' },
      'one': { id: 'one', content: '1' },
      'three': { id: 'three', content: '3' },
      'two': { id: 'two', content: '2' },
    }
};
export default initialData;

با به خاطر سپردن این سه مفهوم اصلی می‌توانیم شروع به ساخت نخستین کامپوننت خود بکنیم. این کامپوننت تنها شامل متد render است و قصد داریم در ادامه آن را تکمیل کنیم:

class NumbersGame extends React.Component<any, INumbersGameState>{
    
  public constructor(props: any) {
    super(props);
    this.onDragEnd = this.onDragEnd.bind(this);
    this.state = {...initialData};
  }

  public onDragEnd(result: any) {
    // the item was dropped!
  }
    
  public render() {
    const numbers = this.state.column.numberIds.map((numberId: string) => this.state.numbers[numberId]);

    return (
      <NumbersGameContext onDragEnd={this.onDragEnd}>
        <VerticalColumn column={this.state.column} items={numbers} />
      </NumbersGameContext>
    )
  }
}

NumbersGameContext: این تنها یک پوشش برای DragDropContext است.

VerticalColumn

این ستونی است که شامل عناصر کشیدنی هستند.

export default (props: IVerticalColumnProps) =>
  <DroppableWrapper droppableId={props.column.id} className="source">
    <DraggableListItems items={props.items} />
</DroppableWrapper>

DroppableWrapper کامپوننتی است که ایده کانتینر Droppable را تجرید می‌کند و از این رو مشخصات خاصی دارد که در ادامه بیشتر توضیح می‌دهیم. شیوه تعریف آن به صورت زیر است:

export default (props: any) =>
  <Droppable droppableId={props.droppableId}>
    {(provided: any) => (
      <div className={props.className}
            ref={provided.innerRef}
            {...provided.droppableProps}
            {...provided.droppablePlaceholder}>
              {props.children}
      </div>
    )}
</Droppable>

Droppable باید یک droppableId داشته باشد که درون DragDropContext یکتا باشد. همچنین یک تابع را به عنوان فرزند می‌پذیرد و از render props pattern برای جلوگیری از ایجاد یا دستکاری DOM استفاده می‌کند. در واقع شما تنها فردی هستید که می‌توانید عناصر DOM را ایجاد کنید.

آرگومان اول این تابع provided است. این آرگومان دارای خصوصیت droppableProps است که برای معرفی کامپوننت به عنوان یک droppable ضروری است. برخی از آن‌ها را می‌توان با استفاده از عملگر spread مورد استفاده قرار داد و در این راهنما از عملگر spread استفاده خواهیم کرد.

خصوصیت دیگر innerRef است که تابعی است که برای ارائه عنصر DOM به کتابخانه مورد استفاده قرار می‌گیرد.

در نهایت باید یک placeholder درج کنیم. placeholder یک عنصر ری‌اکت است که برای افزایش فضای موجود در یک droppable در طی کشیدن یک آیتم استفاده می‌شود. Droppable باید به عنوان فرزند کامپوننتی که به droppable اختصاص یافته است اضافه شود. Droppable ما اینک کامل شده است.

اکنون زمان آن رسیده است که DraggableListItems خود را کدنویسی کنیم. این کامپوننت NumberBox را ایجاد می‌کند که یک آیتم droppable است.

export default (props: IDraggableListItems) =>
  <div> {props.items.map(toNumberBox)} </div>

function toNumberBox(item: INumberItemProps, position: number) {
  return <NumberBox key={item.id} 
                    className="box" 
                    itemPosition={position} 
                    value={item.id} 
                    content={item.content} />
}

NumberBox یک پوشش برای Draggable ایجاد می‌کند:

export default (props: IDraggableItem) => {
  const className = `dnd-number size-${props.value}`;
 
  return (
    <DraggableItemWrapper draggableId={props.value} 
                          index={props.itemPosition} 
                          className={className}>
      <div>{props.content}</div>
    </DraggableItemWrapper>
  )
}

اکنون یک DraggableItemWrapper ایجاد کرده‌ایم که ایده Draggable را تجرید می‌کند، زیرا همانند Droppable مشخصات خاصی دارد که باید تعریف شوند.

export default (props: any) =>
  <Draggable draggableId={props.draggableId} index={props.index}>
    {(provided: any) => (
      <div className={props.className} 
           ref={provided.innerRef} 
           {...provided.draggableProps} 
           {...provided.dragHandleProps}>
        {props.children}
      </div>
    )}
</Draggable>

ما اینک یک الگوی render props دیگر به دست آورده‌ایم. Draggable به یک draggableId و index نیاز دارد. یک draggableId باید درون DragDropContext یکتا باشد. ضمناً یک Draggable به یک تابع به عنوان فرزند خود نیاز دارد. آرگومان نخست همانند Droppable یک provided است. خصوصیت دیگر dragHandleProps است. این‌ها خصوصیات ضروری هستند که برای اختصاص کامپوننت به عنوان یک dragHandleProps نیاز داریم.

با استفاده از این تجرید، می‌توانیم از DraggableItemWrapper استفاده کنیم و از مشخصات سطح پایین اجتناب نماییم.

اینک ما تجرید زیر را با استفاده از مفاهیم اصلی کتابخانه تعریف کرده‌ایم:

  • NumbersGameContext: یک تجرید از DragDropContext
  • DroppableWrapper: یک تجرید از Droppable
  • DraggableItemWrapper: یک تجرید از Draggable

و کامپوننت‌های ما بخشی از بازی هستند:

  • VerticalColumn: کامپوننتی است که ستون droppable و آیتم‌های draggable را در خود جای می‌دهد.
  • DraggableListItems: کامپوننتی است که همه عناصر NumberBox را در خود جای می‌دهد.
  • NumberBox: کامپوننت draggable بازی است.

اما onDragEnd ما هنوز خالی است. برای پر کردن آن ابتدا باید آرگومان این متد را تحلیل کنیم:

 result: {
   destination: {
     droppableId: "column-1"
     index: 2
   }
   draggableId: "four"
   reason: "DROP"
   source: {
     droppableId: "column-1"
     index: 0
   }
   type: "DEFAULT"
 }

این آرگومان شامل اطلاعاتی در مورد محل درگ کردن (از چه ستون و چه موقعیتی)، محل رها کردن (از کدام ستون و کدام موقعیت) و همچنین تعیین نوع کشیدن یا رها کردن است.

با بهره‌گیری از این اطلاعات می‌توانیم به صورت زیر عمل کنیم. ابتدا destination, draggableId و source را در متغیرهای مختلف ذخیره می‌کنیم:

const { destination, source, draggableId } = result;

سپس یک فهرست از اعداد مرتب شده ایجاد می‌کنیم:

const column = this.state.column;
const numberIds = Array.from(column.numberIds);
numberIds.splice(source.index, 1);
numberIds.splice(destination.index, 0, draggableId);
const numbers = numberIds.map((numberId: string) =>
  parseInt(this.state.numbers[numberId].content, 10));

و حالت را به‌روز می‌کنیم:

const newColumn = {
  ...column,
  numberIds
};

this.setState({
  ...this.state,
  column: newColumn
});

یک مرحله دیگر این است که وقتی کاربر عددی را می‌کشد یا برنده می‌شود، یک صدای قبلاً دانلود شده در پوشه assets را پخش می‌کنیم:

public playSound(numbers: number[]) {
  const sound = isSortedAsc(numbers) ? ClapsSound : MoveSound;
  new Audio(sound).play();
}

بدین ترتیب متد کامل به صورت زیر درمی‌آید:

public onDragEnd(result: any) {
  const { destination, source, draggableId } = result;
  if (!destination) { return }
 
  const column = this.state.column;
  const numberIds = Array.from(column.numberIds);
  numberIds.splice(source.index, 1);
  numberIds.splice(destination.index, 0, draggableId);
  const numbers = numberIds.map((numberId: string) => 
    parseInt(this.state.numbers[numberId].content, 10));
 
  this.playSound(numbers);
  this.updateState(column, numberIds);
}

سخن پایانی

ما با داشتن این کامپوننت‌ها، آنچه برای نمایش یک فهرست از کادرهای عددی و توانایی کشیدن و رها کردن آن‌ها در یک ستون مورد نیاز است را گرد هم آورده‌ایم. به عنوان جزییات اضافی می‌توان از CSS و صوت برای ایجاد ظاهری جذاب‌تر برای بازی استفاده کرد.

البته این یک راهنمای مقدماتی محسوب می‌شود و هنوز موارد زیاد دیگری در مورد این کتابخانه جالب وجود دارند که می‌توان مورد بررسی قرار داد. ما این بخش را بر عهده شما قرار می‌دهیم.

اگر این مطلب برایتان مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
devartis
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *