React Hooks로 드래그 앤 드롭 목록 만들기

React Functional Component 내에서 HTML Drag and Drop API을 사용하고 useState() hook을 활용하여 상태를 관리할 것입니다.

결과:





기본 사항



전체 API 설명서를 읽는 것이 좋지만 가장 중요한 사항은 다음과 같습니다.

드래그할 항목



draggable 속성을 true로 설정하고 onDragStart 이벤트 핸들러를 첨부하여 끌 수 있는 DOM 요소를 정의합니다.

<div draggable="true" onDragStart={startDragging}>
 Drag Me 🍰
</div>


드롭 장소



드롭 영역을 정의하려면 onDrop onDragOver 이벤트 핸들러가 연결되어 있어야 합니다.

<section onDrop={updateDragAndDropState} onDragOver={receiveDraggedElements}>
 Drop here 🤲🏻
</section>


이 예에서 각 목록 항목은 드래그하여 동일한 목록을 재정렬하고 드래그되는 항목의 위치와 드롭하려는 위치에 대해 알아야 하기 때문에 드래그 가능한 요소이자 드롭 영역이 됩니다. 거기에서 렌더링되는 목록 항목의 배열을 다시 계산하고 업데이트합니다.

DataTransfer 개체 정보



API는 끌어온 데이터와 상호 작용하기 위한 this objectsetData()getData() 와 같은 몇 가지 편리한 메서드를 제공합니다. 많은 DnD 구현에서 볼 수 있기 때문에 언급하고 싶었지만 React 상태가 있고 Hooks를 가지고 놀고 싶기 때문에 사용하지 않을 것입니다!

Click here 다양한 드래그 가능 요소와 드롭 영역이 있는 드래그 앤 드롭과 DataTransfer 개체 사용의 예를 참조하십시오.

다이빙하자



참고: 스타일 지정에 중점을 두지 않을 것입니다. 이 예제를 복제하는 경우 CodePen에서 SCSS를 자유롭게 복사하십시오.

베어본 구성 요소:




const items = [
  { number: "1", title: "🇦🇷 Argentina"},
  { number: "2", title: "🤩 YASS"},
  { number: "3", title: "👩🏼‍💻 Tech Girl"},
  { number: "4", title: "💋 Lipstick & Code"},
  { number: "5", title: "💃🏼 Latina"},
]


// The only component we'll have:
// It will loop through the items
// and display them.
// For now, this is a static array.
const DragToReorderList = () => {

  return(
    <section>
      <ul>
        {items.map( (item, index) => {
          return(
            <li key={index} >
              <span>{item.number}</span>
              <p>{item.title}</p>
              <i class="fas fa-arrows-alt-v"></i>
            </li>
          )
        })}
      </ul>
    </section>
  )
}


ReactDOM.render(
  <DragToReorderList />,
  document.getElementById("root")
);


항목을 드래그할 수 있도록 설정



2가지가 필요합니다.
  • draggable 속성
  • onDragStart 이벤트 핸들러

  • const onDragStart = (event) => {
      // It receives a DragEvent
      // which inherits properties from
      // MouseEvent and Event
      // so we can access the element
      // through event.currentTarget
    
      // Later, we'll save
      // in a hook variable
      // the item being dragged
    }
    



    <li key={index} draggable="true" onDragStart={onDragStart}>
      <span>{item.number}</span>
      <p>{item.title}</p>
      <i class="fas fa-arrows-alt-v"></i>
    </li>
    
    



    멋진! 이제 드래그할 수 있습니다.

    드롭 영역으로 변환



    2개의 이벤트 핸들러가 필요합니다.
  • onDrop
  • onDragOver

  • 
    const onDragOver = (event) => {
      // It also receives a DragEvent.
      // Later, we'll read the position
      // of the item from event.currentTarget
      // and store the updated list state
    
      // We need to prevent the default behavior
      // of this event, in order for the onDrop
      // event to fire.
      // It may sound weird, but the default is
      // to cancel out the drop.
      event.preventDefault();
    }
    
    const onDrop = () => {
      // Here, we will:
      // - update the rendered list
      // - and reset the DnD state
    }
    



    <li 
      key={index} 
    
      draggable="true" 
      onDragStart={onDragStart}
    
      onDragOver={onDragOver}
      onDrop={onDrop}
    >
      <span>{item.number}</span>
      <p>{item.title}</p>
      <i class="fas fa-arrows-alt-v"></i>
    </li>
    


    기본 동작here에 대해 자세히 읽어보십시오. 문서의 해당 부분을 읽을 때까지 몇 시간의 작업 시간을 잃었습니다. 🤷🏼‍♀️

    또한 onDragEnter를 사용하여 현재 호버링된 드롭 영역에 일부 스타일을 설정할 수 있습니다.

    onDragEnter fires once, whereas onDragOver fires every few hundred milliseconds, so it's ideal to add a css class for instance.



    즉, 나는 onDragEnter 가 신뢰할 수 없는 것으로 나타났기 때문에 onDragOver 에서 일부 상태/플래그를 확인하고 onDragEnter 대신 이를 기반으로 스타일 업데이트를 수행하기로 했습니다.

    또한 스타일을 제거하기 위해 드롭 영역을 가리키면 실행되는 onDragLeave를 사용할 수 있습니다.

    역동적으로 만들기



    기능적 구성 요소에서 React 상태를 사용할 수 있으려면 변수와 업데이트 기능을 제공하는 useState 후크를 사용합니다. 💯

    우리는 그것들 중 2개를 가질 것입니다:
  • 드래그 앤 드롭 상태를 추적하기 위한 1개
  • 1은 렌더링된 목록 상태를 저장합니다.

  • const initialDnDState = {
      draggedFrom: null,
      draggedTo: null,
      isDragging: false,
      originalOrder: [],
      updatedOrder: []
    }
    
    const items = [
      { number: "1", title: "🇦🇷 Argentina"},
      { number: "2", title: "🤩 YASS"},
      { number: "3", title: "👩🏼‍💻 Tech Girl"},
      { number: "4", title: "💋 Lipstick & Code"},
      { number: "5", title: "💃🏼 Latina"},
    ]
    
    const DragToReorderList = () => {
    
      // We'll use the initialDndState created above
    
      const [dragAndDrop, setDragAndDrop] = React.useState( initialDnDState );
    
      // The initial state of "list"
      // is going to be the static "items" array
      const [list, setList] = React.useState( items );
    
      //...
    
      // So let's update our .map() to loop through
      // the "list" hook instead of the static "items"
      return(
       //...
       {list.map( (item, index) => {
         return(
           // ...
         )
       })}
       //...
       )
    }
    


    onDragStart 연결



    이 함수는 드래그 시작을 처리합니다.

    먼저 data-position 속성을 추가하고 각 항목의 index를 저장합니다.

    <li
      data-position={index}
      //...
    >
    


    그 다음에:

    const onDragStart = (event) => {
    
      // We'll access the "data-position" attribute
      // of the current element dragged
      const initialPosition = Number(event.currentTarget.dataset.position);
    
      setDragAndDrop({
        // we spread the previous content
        // of the hook variable
        // so we don't override the properties 
        // not being updated
        ...dragAndDrop, 
    
        draggedFrom: initialPosition, // set the draggedFrom position
        isDragging: true, 
        originalOrder: list // store the current state of "list"
      });
    
    
      // Note: this is only for Firefox.
      // Without it, the DnD won't work.
      // But we are not using it.
      event.dataTransfer.setData("text/html", '');
     }
    
    


    onDragOver 연결




     const onDragOver = (event) => {
      event.preventDefault();
    
      // Store the content of the original list
      // in this variable that we'll update
      let newList = dragAndDrop.originalOrder;
    
      // index of the item being dragged
      const draggedFrom = dragAndDrop.draggedFrom; 
    
      // index of the drop area being hovered
      const draggedTo = Number(event.currentTarget.dataset.position); 
    
      // get the element that's at the position of "draggedFrom"
      const itemDragged = newList[draggedFrom];
    
      // filter out the item being dragged
      const remainingItems = newList.filter((item, index) => index !== draggedFrom);
    
      // update the list 
      newList = [
        ...remainingItems.slice(0, draggedTo),
        itemDragged,
        ...remainingItems.slice(draggedTo)
      ];
    
       // since this event fires many times
       // we check if the targets are actually
       // different:
       if (draggedTo !== dragAndDrop.draggedTo){
         setDragAndDrop({
         ...dragAndDrop,
    
          // save the updated list state
          // we will render this onDrop
          updatedOrder: newList, 
          draggedTo: draggedTo
         })
      }
    
     }
    


    마지막으로, 버려! 🌟




    const onDrop = () => {
    
      // we use the updater function
      // for the "list" hook
      setList(dragAndDrop.updatedOrder);
    
      // and reset the state of
      // the DnD
      setDragAndDrop({
       ...dragAndDrop,
       draggedFrom: null,
       draggedTo: null,
       isDragging: false
      });
     }
    


    엄청난!



    이 펜에 대한 전체 코드 예제를 가져옵니다.



    https://codepen.io/florantara/pen/jjyJrZ

    이 API에 대한 단점


  • 모바일 장치에서는 작동하지 않으므로 mouse events으로 구현해야 합니다.
  • browser compatibility은 더 좋아졌지만 대중에게 공개되는 제품을 만드는 경우 철저히 테스트해야 합니다.

  • 마음에 드셨다면 자유롭게 공유해주세요 💓

    좋은 웹페이지 즐겨찾기