Skip to content

드래그 앤 드랍 삽질기

박재윤 edited this page Jan 18, 2021 · 3 revisions

드래그 앤 드랍

거래내역 페이지의 구조

  1. menuBar header
    • page nav
    • date nav
  2. list container
    • filter container
      • inout filter
      • category filter
    • transactions
      • transactions of one day
        • transaction item

첫 시도

처음에 시도했던 것은 드랍이 될 수 있는 위치에 미리 표시를 해주는 것이 아니라 해당 날짜에 드랍이 되게 하는 기능을 구현하는 것 만을 시도했다. 인터넷에서 여러가지 예제를 보았는데 dataTransfer로 object를 전달을 하는 것은 힘들어보였다. 따라서 어떤 거래내역이 드래그가 되어 있는지는 onDragEnd에서 알 수 있으므로 transactionsOfOneDay 컴포넌트에 onDragEnter일 때 현재 hover 되어 있는 날짜를 dataTransfer에 저장해놓고 onDragEnd에서 해당 날짜를 dataTransfer에서 불러와 수정을 하도록 했다.

그런데 onDragEnd에서 dataTransfer에 날짜를 가져오려고 시도하니 계속 빈 문자열이 나왔다. onDragOver에서도 확인을 해보니 빈 문자열만 나왔다. 이후에 onDrop에서 dataTrnasfer 값을 확인해보니 값이 잘 들어와있었다. 알고보니 onDrop에서만 dataTransfer의 값이 나왔다. 그런데 onDrop은 transactionsOfOneDay 컴포넌트에 있기 때문에 날짜를 전달하는 것은 의미가 없고 어떤 transactionItem이 드래그가 되었는지를 알아야했다. 따라서 dataTransfer로 날짜를 전달하는 것이 아니라 transactionItem의 _id를 전달했다. 그리고 transactionOfOneDay의 onDrop에서 _id를 불러와 해당 거래내역의 날짜를 수정해주었다.

두번째 시도

그런데 드래그를 할 때 각 transactionsOfOneDay에 hover를 하면 날짜가 들어갈 위치를 미리 보여주는 것을 통해 UX를 좀 더 향상시키고 싶었다. 그런데 만약 하루치 거래내역들 사이에서도 순서를 바꿀 수 있게 하려면 거래내역들 사이에 순서를 나타내는 것이 있어야 하고 DB의 구조도 변경해야 했기 때문에 transactionsOfOneDay의 가장 마지막에 들어가는 것으로 결정했다.

원했던 것

현재 거래내역 목록에 보이는 것은 필터링이 된 filteredTransactions 안에 있는 내용들이 보이는 것이었다. 실제로 드래그를 하는 동안에 이 filteredTransactions를 변경시켜서 거래내역이 옮겨질 위치를 표시를 할 것인가 고민을 했다. 그런데 store의 filteredTransactions를 변경시키는 것은 데이터의 일관성을 해치고 순간적으로 거래내역의 총 합도 변화시킬 것이기 때문에 store의 데이터를 변경시키는 것은 좋은 선택이 아니라고 생각했다.

따라서 각 transactionsOfOneDay의 마지막에 transactionsOfOneDay의 날짜와 현재 hover 되어 있는 날짜가 같으면 transactionItem을 렌더링 하도록 구현을 했다. 이를 위해서 draggedInDate라는 state와 draggedItem이라는 state를 만들었다. 그래서 onDragStart에서 draggedItem을 지정해주고 onDraggedEnter에서 draggedInDate를 해당 날짜로 변경시켜주었다.

오류 수정

깜빡이는 문제

가장 상위의 transactionsOfOneDay의 onDrageEnter, onDragLeave에만 이벤트를 걸어놨는데 드래그를 하면 내부의 다른 요소들이 target으로 이벤트가 호출되는 경우들이 있었다. 각 transactionItem에 들어갈 때 child element로 들어갈 때 onDragEnter, onDragleave가 불렸다. 그래서 표시를 해주는 역할을 하는 transactionItem이 깜빡깜빡 하는 문제가 있었다.

transactionOneDay 안의 children으로 들어갈 때 transactionOneDay의 onleave가 호출되고 setDraggedIn이 false로 순간적으로 바뀌었다가 다시 onDragEnter 때문에 true로 바뀌면서 깜빡이는 문제였다.(gif에서는 잘 보이지 않는다..)

이를 해결하기 위해서 onEnter 할 때 +1 해주고 onLeave 할 때 -1을 해줘서 0이 될 때만 setDraggedIn을 false로 해주면 되지 않을까 했다. 그래서 dragDepth라는 state를 만들어서 관리했는데 다음과 같이 log을 출력했을 때 state가 setState 이후에 즉시 바뀌지 않았다.

console.log('drag leave before:', dragDepth, e.target);
setDragDepth(() => dragDepth - 1);
console.log('drag leave after:', dragDepth, e.target);

그리고 dragenter에서 값이 바뀐 것을 보았는데 dragleave에서는 state 값이 바뀌지 않는 것으로 나왔다.

공식문서를 참고해보니 setState는 즉각적인 명령이 아니라 요청이라고 했다. setState의 연이은 호출은 같은 주기 내의 바로 직전 호출 결과를 덮어쓴다. setState는 이벤트 핸들러의 끝에서 함께 flushed 된다. 참고

setState를 이용해서 반응성이 빠른 것을 만드는 것이 무리가 있을 것 같았다.

onleave 검사를 마우스 커서의 위치를 가지고 계산을 하는 방식으로 수정했다. cursor position element position 참고

onDragEnteronDragLeave를 사용하지 않고 onDragOver를 사용하면 이런 문제가 해결이 되었다. onDragLeave가 하위 컴포넌트로 들어갈 때 불리는 것이 문제였기 때문에 onDragLeave를 사용하지 않도록 수정했고 매우 빠르게 들어갈 경우 onDragEnter가 호출이 되지 않는 경우가 있었기 때문에 지속적으로 체크를 하도록 onDragEnter에서 draggedInDate를 지속적으로 set 해주었다. setState가 batch가 되어서 처리가 되기 때문에 성능적 이슈가 없을 것이라고 생각해 이렇게 수정했다.

표시를 해주는 transactionItem이 남아있는 문제

만약 드래그를 하다가 드랍 영역이 아닌 영역에서 드랍을 했을 경우에 onDragLeave에서 해제를 해주는 것을 삭제했기 때문에 계속 남아있는 문제가 있었다. 알맞은 drop 위치의 onDrop에서는 draggedInDatedraggedItem을 초기화 해주기 때문에 문제가 없었지만 drop 영역 밖에서는 state를 초기화 해주지 않기 때문에 생긴 문제였다. 따라서 onDragEnd에서 dropEffect가 none일 경우에 초기화를 해주었다.

표시를 해주는 transactionItem이 남아있는 문제2

드래그를 매우 빠르게 한 다음에 돌아온 뒤 드래그를 끝낼 경우에 표시를 해주는 trasactionItem이 남아있는 문제가 있었다. 매우 빠르게 돌아와 드래그를 끝낼 경우에 다시 돌아온 transactionOfOneDay에서 onDragOver가 호출이 되지 않는 문제가 있었다. 현재 상태가 복잡하기 때문에 onDragOver가 호출이 늦게 되는 것인가 의문이 들어서 매우 간단한 test를 만들어서 확인을 해봤지만 매우 빠르게 움직이면 onDragOver가 호출되지 않았다.

따라서 onDragEnd에서 모든 경우에 state를 초기화를 해주었다. 그러니 만약 제대로 된 drop 공간에 갔을 때 먼저 state를 초기화해 표시해주는 transactionItem이 사라지고 그 뒤에 옮겨지는 transactionItem이 filteredTransactions의 변화로 생기기 때문에 살짝 깜빡이는 문제가 있었다. 따라서 onDragEnd에서 setTimeout을 이용해 약간의 시간 뒤에 state를 초기화 하도록 했다.

Clone this wiki locally