-
Notifications
You must be signed in to change notification settings - Fork 6
드래그 앤 드랍 삽질기
- menuBar header
- page nav
- date nav
- list container
- filter container
- inout filter
- category filter
- transactions
- transactions of one day
- transaction item
- transactions of one day
- filter container
처음에 시도했던 것은 드랍이 될 수 있는 위치에 미리 표시를 해주는 것이 아니라 해당 날짜에 드랍이 되게 하는 기능을 구현하는 것 만을 시도했다. 인터넷에서 여러가지 예제를 보았는데 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 검사를 마우스 커서의 위치를 가지고 계산을 하는 방식으로 수정했다.
참고
onDragEnter
와 onDragLeave
를 사용하지 않고 onDragOver
를 사용하면 이런 문제가 해결이 되었다. onDragLeave
가 하위 컴포넌트로 들어갈 때 불리는 것이 문제였기 때문에 onDragLeave
를 사용하지 않도록 수정했고 매우 빠르게 들어갈 경우 onDragEnter
가 호출이 되지 않는 경우가 있었기 때문에 지속적으로 체크를 하도록 onDragEnter
에서 draggedInDate
를 지속적으로 set 해주었다. setState가 batch가 되어서 처리가 되기 때문에 성능적 이슈가 없을 것이라고 생각해 이렇게 수정했다.
만약 드래그를 하다가 드랍 영역이 아닌 영역에서 드랍을 했을 경우에 onDragLeave
에서 해제를 해주는 것을 삭제했기 때문에 계속 남아있는 문제가 있었다. 알맞은 drop 위치의 onDrop
에서는 draggedInDate
와 draggedItem
을 초기화 해주기 때문에 문제가 없었지만 drop 영역 밖에서는 state를 초기화 해주지 않기 때문에 생긴 문제였다. 따라서 onDragEnd
에서 dropEffect가 none일 경우에 초기화를 해주었다.
드래그를 매우 빠르게 한 다음에 돌아온 뒤 드래그를 끝낼 경우에 표시를 해주는 trasactionItem이 남아있는 문제가 있었다. 매우 빠르게 돌아와 드래그를 끝낼 경우에 다시 돌아온 transactionOfOneDay에서 onDragOver
가 호출이 되지 않는 문제가 있었다. 현재 상태가 복잡하기 때문에 onDragOver
가 호출이 늦게 되는 것인가 의문이 들어서 매우 간단한 test를 만들어서 확인을 해봤지만 매우 빠르게 움직이면 onDragOver
가 호출되지 않았다.
따라서 onDragEnd
에서 모든 경우에 state를 초기화를 해주었다. 그러니 만약 제대로 된 drop 공간에 갔을 때 먼저 state를 초기화해 표시해주는 transactionItem이 사라지고 그 뒤에 옮겨지는 transactionItem이 filteredTransactions의 변화로 생기기 때문에 살짝 깜빡이는 문제가 있었다. 따라서 onDragEnd
에서 setTimeout을 이용해 약간의 시간 뒤에 state를 초기화 하도록 했다.