[Swap(), 그 기록] 6. 드래그 앤 드롭 (feat. useRef)

Soye Park·2023년 2월 20일
0
post-thumbnail

이 포스팅은 사이드 프로젝트를 진행하며 생겼던 여러 일들에 대해서 기록을 겸하여 작성한 글입니다. 주기적으로 작성하고자 노력은 하지만 블로그 포스팅 한번 하려고 하면 머릿속에서 나만의 언어로 정리도 하면서 작성하다가 몇시간은 사용하는 지라 주기적이지 않을 가능성이 농후합니다. 아자아자...

드래그 앤 드롭(Drag And Drop)

들어가면서...

나는 살면서 단 한번도 인형뽑기에 성공해본 적이 없다. 들었다가 옮기면 스르르 하고 흘러내리는 것이... 내 팔자에는 인형을 뽑는 건 없다고 생각했다. 그래서 떠올렸다. 나는 내 눈 앞에 보이는 저 코드 덩어리는 들어서 옮길 수 있겠지... 아... 쉽지 않더라 그래도... 괜찮다 했으니 됐다.


드래그 앤 드롭을 위한 이벤트들

  • onDragOver

    일반적으로 HTML 요소는 다른 요소 위에 위치할 수 없으나 onDragOver의 기본이벤트를 막는 것만으로 다른 요소 위에 위치할 수 있게 된다.

  • onDragStart

    요소를 드래그하는 시점에서 이벤트가 발생

  • onDragEnter

    드래그 중인 요소가 다른 요소 위에 올라가게 되면 이벤트가 발생

  • onDragEnd

    드래그 중에 마우스 클릭을 그만두면 발생


기능 구현 시작

우선 대상을 드래그 가능한 요소로 변경! - draggable

드래그 앤 드롭 이벤트를 만들기 위해서는 당연히 요소를 드래그할 수 있는 속성을 부여해야한다. draggable은 HTML에서 제공하고 있는 Drag And Drop API에 포함되어있는 속성인데 말그대로 요소를 드래그 가능하게 만들어준다.

<div draggable>

useRef를 활용해서 드래그될 놈과 드래그될 곳을 설정하자!

자바스크립트에서는 이 값을 담아두고 보관하기가 까다로와 드래그 앤 드롭이 까다롭지만, 리액트에서는 useRef를 통해 간단하게 좌표가 될 인덱스 넘버 또는 요소의 value를 저장할 수 있다.

  const itemBeDragged = useRef<number>(0);
  const locationBeDragged = useRef<number>(0);

두 경우 모두 이벤트가 발생할 때마다 useRef의 current 값에 할당해줄 수 있도록 하면 되는데, imgUrl 들의 배열을 map해 렌더링을 하고 있으므로 index 값을 손 쉽게 받아올 수 있다.

{imgUrl.map((item, index) => {
  return (
    <PreviewComponent
      onDragStart={() => takeItem(index)}
      onDragEnter={() => enterItemInLocationBeDragged(index)}

	...
})}
  1. 드래그될 놈!

드래그 될 놈은 onDragStart 이벤트가 발생할 때의 인덱스를 받아오면 된다.

onDragStart={() => takeItem(index)}
  1. 드래그될 곳-

드래그 될 위치는 결국 이 요소가 마지막으로 닿는 곳이므로 onDragEnter를 통해 계속해서 추적해 인덱스를 업데이트 해주면 된다.

onDragEnter={() => enterItemInLocationBeDragged(index)}

마지막으로 Drop이 되었을 때 잘 드롭되어 위치이동할 수 있게 하기!

🚨 참고 🚨
드랍 이벤트의 경우는 slice를 활용해 기존의 배열을 수정하는 형태로 한다. 하지만 무조건 "복사"를 해야한다.
이번에는 뎁스가 없는 배열이기 떄문에 얕은 복사를 하지만 만일을 위해 lodash를 활용하거나 만들어둔 깊은 복사 로직을 이용해 깊은 복사를 해주는 것이 좋다

자 준비는 다 끝났으니 Drop함수를 만들면 되는데, 2가지의 작업을 먼저 진행한다.

  1. 스테이트에 저장되어있는 "원본 배열"을 복사하는 것
const copyListItems = [...imgUrl]
  1. 복사된 배열과 onDragStart로 확보했던 드래그된 요소의 기존 인덱스 넘버를 활용해 value를 얻는 것 (useRef의 current값 활용)
const itemBeDraggedContent = copyListItems[itemBeDragged.current];
  1. 드래그된 놈을 원래 위치에서 "삭제"하고 마지막으로 닿았던 곳에 "추가"하기
// 현재 드래그 된 요소가 드랍되면 요소를 삭제
copyListItems.splice(itemBeDragged.current, 1);
// 드랍된 요소가 마지막으로 닿았던 요소의 인덱스 넘버에 드래그 됐다가 드랍된 요소를 추가
copyListItems.splice(locationBeDragged.current, 0, itemBeDraggedContent);
  1. 이렇게 만들어진 배열을 setState 함수를 활용해 리렌더될 수 있도록 하면 끝! (물론 useRef의 값을 기본 값으로!)
itemBeDragged.current = 0;
locationBeDragged.current = 0;

setImgUrl(copyListItems);

결과물


마치며...

정말 간단한 DND 였으나 그래도 어느정도 헤매인 것 같다. 사실 React-beautiful-dnd 와 같은 라이브러리도 있어서 라이브러리 쓰는 게 제일 편하고 안전하고 여러가지 좋은 효과들도 있고 뭐 그러겠지마는,,, 그냥 한번 구현해보고 싶어서 인터넷 뒤져가며 해봤다. 이런 동작원리를 가졌다는 것을 알았으니 다음에는 바닐라 자바스크립트로도 도전해보면 좋을 것 같기도?

profile
응애FE개발자/ 블로그 이전 : https://soyeah-log.vercel.app/

0개의 댓글