drag and drop 기능 구현과 최적화까지

HR·2023년 5월 23일
0

스택티콘

목록 보기
5/5
post-custom-banner

스택티콘의 메인에 있는 아래 화면.

선택된 요소들이 input 창 안에 보인다.

선택된 요소들의 순서대로 이미지가 생성되고 있지만, 드래그 앤 드랍으로 순서를 변경할 수 있게 하면 좋겠다는 피드백이 들어왔다.

당장 해결 방법이 명확하게 떠오르지 않아, 임시 프로젝트를 새로 만들어 기능 구현을 먼저 해봤다.

구현 과정

큰 그림

DragContainer.tsx
DraggableItem.tsx

DraggableItem에서 위의 각 chip에 해당하는 요소들을 담당하고, input에 해당하는 부분을 DragContainer로 생성했다.

작은 부분에서부터 bottom-up으로 구현했다.

DraggableItem

1. drag 가능하다는 것을 알려주기

해당 요소가 드래그가 가능하다는 것을 알려주는 방법으로, 태그의 속성 중에 draggable이라는 속성이 있다. 해당 속성을 true로 설정하면 된다.

<div
  draggable
  ...
>
  {...}
</div></div>

2. dragStart, dragOver

사용할 두 가지 리스너는 onDragStart, onDragOver 두 가지이다. DragContainer로부터 받은 두 개의 함수를 draggable한 요소에 추가해준다.

<div
  draggable
  onDragStart={dragStart}
  onDragOver={dragOver}
>
  {...}
</div>

DragContainer

1. array, selected

배열을 관리하기 위한 array, 선택된(=드래그중인) 아이템을 가지고 있기 위한 selected 두 개의 상태를 사용한다.

const [array, setArray] = useState([]);
const [selected, setSelected] = useState('');

2. dragStart, dragOver

앞서 draggableItem에서 사용했던 두 함수를 실제 구현한다.

const dragStart = (index) => {
  setSelected(array[index]);
};

const dragOver = (index) => {
  reorderArray(index);
}

dragStart는 드래그 이벤트가 시작될 때 호출되는 함수로, 현재 드래그 중인 요소를 탐지하기 위해 사용한다.

dragOver는 drag 이벤트가 발생하고 있는 동안에 주기적으로 호출된다. 현재 드래그 중인 요소가 지나고 있는 요소들을 출력해준다.

3. reorderArray

드래그 중인 요소를 제거하고, 드래그를 멈춘 지점에 드래그 중이었던 요소를 집어넣는다.

const reorderArray = (index) => {
  const filteredArray = array.filter(el => el !== selected);

  setArray([
    ...filteredArray.slice(0, index), 
    selectedStack,
    ...filteredArray.slice(index, filteredArray.length);
  ]);  
}

로직은 아래와 같다.

  1. 먼저 선택한 요소를 배열에서 제거한다.
  2. 넣을 위치를 기준으로, 배열을 앞 뒤로 자른다.
  3. 잘린 배열 앞부분 + 드래그 끝난 요소 + 잘린 배열 뒷부분 으로 기존의 배열을 업데이트한다.

문제점과 해결 - debounce

위에서 사용한 onDragOver에서 문제가 발생했다. 드래그를 하는 동안 계속해서 호출이 되기 때문에, 아래와 같이 엄청난 횟수로 호출이 된다.

위의 dragOver 함수에서 reorderArray를 호출하는 부분에 디바운싱을 적용하여 해결하였다.

let timer;

const dragOver = (index) => {
  if (timer) {
    clearTimeout(timer);    
  }
  
  timer = setTimeout(() => {
    reorderArray(index);
  }, 200);
}

위 작업을 통해 함수가 호출되는 속도가 굉장히 감소하였고, 이에 따라 렌더링 횟수도 감소하였다.

데모 프로젝트의 전체 코드는 여기에서 확인 가능하다.

post-custom-banner

0개의 댓글