완성본

완성본

TL;DR


  1. 드래그할 수 있는 요소로 변경 — draggable={true}

  2. 드래그 시작 — onDragStart 이벤트 트리거

    1. 드래그하고 있는 요소의 인덱스 정보 저장 — state.draggedFrom
    2. 드래그 상태 true로 변경 — state.isDragging
    3. 이벤트 트리거 시점의 엘리먼트 리스트 저장 — state.originalOrder
  3. 마우스 커서가 드롭 가능한 영역에 있을 때 — onDragOver 이벤트 트리거

    1. drop 이벤트를 사용할 수 있도록 dragOver 기본 이벤트 방지 — e.preventDefault()

    2. 마우스 포인터 위치에 있는 요소의 인덱스 저장 — state.draggedTo

    3. 엘리먼트 순서 변경 — state.updatedOrder

      드래그중인 아이템을 마우스 포인터 위치(draggedTo 인덱스)로 이동. 기존 마우스 포인터 위치에 있던 요소는 바로 뒤로 밀림.

  4. 드롭 가능한 영역에서 드롭했을 때 — onDrop 이벤트 트리거

    1. 순서를 변경한 엘리먼트 리스트 렌더
    2. 드래그앤드롭 관련 상태 초기화

배경 지식


HTML 드래그앤드롭 Web API를 이용해 리스트 엘리먼트의 순서를 마우스 드래그앤드롭으로 바꿀 수 있다. 엘리먼트의 draggable 속성을 true로 주면 해당 요소는 드래그 가능한 객체가 된다. 이미지, 링크, 선택한 텍스트(텍스트 블록)는 기본적으로 드래그할 수 있다. 드래그 가능 상태가 되면 onDragStart 같은 드래그 관련 이벤트를 사용할 수 있다. onDragStart는 드래그가 시작되면 트리거되는 이벤트다.

// 리스트 요소
<div draggable="true" onDragStart={startDragging}>
	Drag Me 🍰
</div>

엘리먼트에 onDroponDragOver 이벤트를 걸면 드롭 가능한 영역(유효한 드롭 대상)이 된다. 이 두 이벤트를 활용해 드래그앤드롭 기능을 구현할 수 있다. onDragOver는 마우스 포인터에 있는 요소가 유효한 드롭 대상일 때 트리거 되며, onDrop은 드롭 가능한 영역에서 드롭했을 때 트리거되는 이벤트다.

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

드래그한 (요소)데이터와 상호 작용하기 위해 setData()getData() 같은 이벤트 메서드를 사용할 수도 있다. 현재 드래그 하고 있는 요소 정보(id 등)를 setData()를 통해 저장하고, 드롭했을 때 getData()를 통해 요소 정보를 불러온 후 재정렬 하는 방식으로 활용할 수 있다.

event.dataTransfer.setData(key, value) // 여러 key를 이용해 다수의 데이터를 저장할 수 있다
event.dataTransfer.getData(key)

상태


리액트 컴포넌트 바깥 영역에 리스트를 렌더할 상태(items)와, 드래그앤드롭과 관련한 상태의 초기값을 정의한다. 드래그앤드롭 상태엔 드래그 중인 요소의 인덱스, 업데이트 전후의 렌더 리스트(items), 드래그 상태를 저장하는 곳이다.

const items = [
  { number: '3', title: '👨🏻‍💻 Tech Man' },
	// 생략
];

const initialDnDState = {
  draggedFrom: null, // 드래그를 시작한 요소의(마우스를 클릭하여 움직인 요소) 인덱스
  draggedTo: null, // 드롭 대상 요소의 인덱스(드래그하여 마우스 커서가 위치한 요소의 인덱스)
  isDragging: false, // 드래그 여부 Boolean
  originalOrder: [], // 드롭하기전(순서가 바뀌기 전) 기존 list
  updatedOrder: [], // 드롭한 후 순서가 바뀐 list
};