# Drag & Drop 를 이용해서 리스트 요소 순서 바꾸기

> [MDN - HTML 드래그 앤 드롭 API](https://developer.mozilla.org/ko/docs/Web/API/HTML_Drag_and_Drop_API)
>
> [MDN - Drag Operations(실습)](https://developer.mozilla.org/ko/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations)
>
> [HTML SPEC - Drag and drop](https://html.spec.whatwg.org/multipage/dnd.html#dndevents)
>
> [Codepan example](https://codepen.io/retrofuturistic/pen/tlbHE)
>
> [How to Implement Drag and Drop Feature for Your React Component](https://www.pluralsight.com/guides/implement-drag-drop-react-component)

`todo list` 를 구현했는데, 뭔가 너무 아쉬웠다. 평소에 구현해보고 싶었던 기술중에 `Drag & Drop` 이 있었는데 `todo list` 에 적용하기 너무 좋아보여서 시도했다.

## 인터페이스

DnD 의 인터페이스로는 크게 4가지가 존재한다.

* [`DragEvent`](https://developer.mozilla.org/ko/docs/Web/API/DragEvent)
* [`DataTransfer`](https://developer.mozilla.org/ko/docs/Web/API/DataTransfer)
* [`DataTransferItem` (en-US)](https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem)
* [`DataTransferItemList` (en-US)](https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItemList)&#x20;

`DateEvet` 와 `DataTranfer` 만 알아보자.

### DragEvent

| 이벤트                                                                         | 이벤트 핸들러                                                                                                   | 설명                                                                                                                                                                  |
| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dragstart (en-US)`                                                         | [`ondragstart` (en-US)](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragstart) | 사용자가 요소나 텍스트 블록을 드래그하기 시작했을 때 발생한다. ([드래그 시작하기](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#dragstart)를 보시오.)           |
| [`drag`](https://developer.mozilla.org/ko/docs/Web/API/Document/drag_event) | [`ondrag` (en-US)](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondrag)           | 요소나 텍스트 블록을 드래그 할 때 발생한다.                                                                                                                                           |
| `dragend (en-US)`                                                           | [`ondragend` (en-US)](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragend)     | 드래그를 끝냈을 때 발생한다. (마우스 버튼을 떼거나 ESC 키를 누를 때) ([드래그 끝내기](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#dragend)를 보시오)        |
| `dragenter (en-US)`                                                         | [`ondragenter` (en-US)](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragenter) | 드래그한 요소나 텍스트 블록을 적합한 드롭 대상위에 올라갔을 때 발생한다. ([드롭 대상 지정하기](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#droptargets)를 보시오.) |
| `dragover (en-US)`                                                          | [`ondragover` (en-US)](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragover)   | 요소나 텍스트 블록을 적합한 드롭 대상 위로 지나갈 때 발생한다. (매 수백 밀리초마다 발생한다.)                                                                                                             |
| `dragleave (en-US)`                                                         | [`ondragleave` (en-US)](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragleave) | 드래그하는 요소나 텍스트 블록이 적합한 드롭 대상에서 벗어났을 때 발생한다.                                                                                                                          |
| `drop (en-US)`                                                              | [`ondrop` (en-US)](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondrop)           | 요소나 텍스트 블록을 적합한 드롭 대상에 드롭했을 때 발생한다. ([드롭하기](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#dragstart)를 보시오.)               |
| `dragexit`                                                                  | [`ondragexit` (en-US)](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragleave)  | 요소가 더 이상 드래그의 직접적인 대상이 아닐 때 발생한다.                                                                                                                                   |

> Drag 관련 이벤트. 여기서 중요한건 이벤트의 발동 주체가 2가지로 나뉜다는것임.

#### 1. 드래그 요소

> 마우스로 클릭하고 끌고 다니는 요소

* drag
* dragStart
* dragEnd

#### 2. 드래그 요소와 겹치는 아이템

> [Define a drop zone](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API#define_a_drop_zone)

* dragEnter
* dragOver (필수로 구현)
* dragLeave
* drop (필수로 구현)

여기서 주목할 건 `Drag` 요소를 다른 요소 어딘가에 놓으면 그에 대한 이벤트는 `drop` 이 발생하고 이 `drop` 을 핸들링 하는 곳이 `Drag` 된 요소가 아니라 **그 요소와 겹치는 아이템이라는 것.**

여기서 의문이 생긴다. `Drag` 된 요소가 아니라 그 요소가 떨어진 곳에서 `Drop` 이벤트 핸들러를 실행한다면, `Drag` 된 요소의 정보는 어떻게 넘겨주는거지? 그 의문은 `DataFransfer` 에서 해결된다.

### DataTransfer

> <https://developer.mozilla.org/ko/docs/Web/API/DataTransfer>
>
> `get`, `set` 이외에도 여러 메서드, 프로퍼티가 있다. 필요하면 위의 링크로 가서 보자.

실제로 드래그를 이용해서 리스트 요소의 순서를 바꾸는걸 생각해보자.

1. 요소를 마우스로 잡는다.
   * 마우스로 잡은 요소에서 `dragStart` 이벤트를 핸들링한다.
2. 끌고 다닌다.
3. 원하는 곳에 내려놓는다.
   * 내려놓은 곳의 요소에서 `drop` 이벤트가 발동된다.

우리는 **1번에서 요소에 대한 정보를 어딘가에 저장해야하고, 그 정보를 3번에서 이용해야한다.** 이걸 해주는게 `DataTransfer`다. 사용법은 매우 간단하다. `setData` 를 통해 데이터를 저장하고, `getData` 를 이용해서 저장된 정보를 불러온다.

> [여기를 참고하자](https://developer.mozilla.org/ko/docs/Web/API/DataTransfer#methods)

실제 사용은 아래처럼 하면된다.

```javascript
...
// 1번. drag 요소의 데이터를 저장
const handleDragStart = (e) => {
  // key는 getData 에서 사용할 key
  // value 는 저장할 데이터
  e.dataTransfer.setData([key], [value]);
}

// 3번. 놓임 당한 곳에서 drag 된 요소 가져오기
const handleDrop = (e) => {
  e.dataTransfer.getData([key]);
  .. 추가 작업들...
}
...
```

`localStorage` 처럼 `get`, `set` 으로 사용하면 된다.

## 실제 사용하기

> [todoList 내의 todo 들을 드래그해서 옮기는 코드를 예로 든다. 코드는 여기를 눌러서 확인하자.](https://github.com/simian114/wanted-preOnBoarding-project8)
>
> 아래의 코드는 미래 재사용을 위해 `useDragAndDrop` 이라는 커스텀훅으로 만들었다.

### 1. draggable 속성 넣기

> 지금까지 내가 만든 웹요소들은 드래그가 모두 불가능했었다. 왜? 바로 `draggable` 속성을 넣지 않았기 때문이다. 즉 `draggable` 속성을 넣으면 요소들은 드래깅이 가능해진다.

```javascript
...
return {
  <TodoItemContainer
      draggable
         ...
    >
}
```

`draggable` 속성을 넣음으로써 API 를 사용할 수 있게 된다.

### 2. onDragStart 핸들러

드래그의 시작을 알려준다. 인터페이스 부분에서 설명했다시피 이 부분에서 옮기는 요소에 대한 정보를 저장해야한다.

```javascript
const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
  setisDragging(true); // 1
  e.dataTransfer.effectAllowed = 'move'; // 2
  e.dataTransfer.setData('text/plain', id); // 3
};
```

코드를 분석해보자면

1. `setIsDragging`
   * 현재 `todo` 가 드래그 하는 중이라는걸 알려주는 `state` 다.
   * 이 값을 통해 `todo` 는 자신이 드래깅중인지 또는 아닌지를 알 수 있다.
     * `css` 핸들링에 사용할 수 있다.
2. `e.dataTransfer.effectedAllowed = move`
   * 드래그를 해보면 디폴트로 마우스 아래에 `+` 버튼이 생긴다. 이 버튼을 막아준다. <https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#drageffects>.
3. `e.dataTransfer.setData('text/plain', id);`
   * **드래그 하는 요소의 정보를 담는다.**

### 3. onDragOver 핸들러

> Dropzone

tkdghk

```javascript
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
  e.preventDefault(); // 1
  setIsDragOver(true); // 2
};
```

1. `e.preventDefault();`
   * 기본으로 발동하는 다른 이벤트들을 막는다. (Touch, pointer events..)
2. `setIsDragOver(true);`
   * 현재 요소가 드래그 요소의 아래 깔려있음을 말해준다.

### 4. onDrop 핸들러

> Dropzone

```javascript
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
  e.preventDefault();
  setisDragging(false);
  setIsDragOver(false);
  const movingTarget = e.dataTransfer.getData('text/plain'); // 1
  callback(movingTarget, id); // 2
};
```

1. `const movingTarget = e.dataTransfer.getData('text/plain');`
   * 이전에 저장한 데이터를 가져온다.
2. `callback()`
   * `todo` 요소의 위치를 바꾸는 함수다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://simian114.gitbook.io/blog/undefined/react/drag-and-drop.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
