Programming/React

[React] useRef를 이용하여 할 일 리스트의 순서 바꾸기

hodo- 2024. 2. 23. 21:44

taskgrow 프로젝트에서 할 일 리스트를 유저가 중요도에 따라 자유롭게 순서를 변환할 수 있도록 UX 개선이 필요하다.
불필요한 리렌더링을 줄이기 위해 useRef를 사용해서 구현했다.

순서를 바꾸는 구현에 있어 생각이 많았다.
단순한 것이지만 드래그라 쉽게 변경 가능함 -> 많은 요청 으로 유저가 최종적으로 수정을 마친 후 한 번만 요청을 보낼 수 있도록 생각을 해보았지만..

  1. 클라이언트에서 순서 저장해두고 있다가 해당 테스크를 벗어나면 변경된 order 서버에 요청 -> 순서 옮기고 브라우저 창을 닫으면 적용 안됨
  2. 순서 변경 버튼 생성 -> 버튼 클릭 자유롭게 순서 변경 가능 -> 확인 버튼 클릭시 변경된 order 서버에 요청 -> 유저 번거로움

와 같은 문제로 딱히 좋은 방법이 떠오르지 않았다.

그래서 기존에 많은 요청을 보낼까봐 걱정했던 게 사실 별 거 아닌 성능 이슈였나 싶기도 하여 우선 드래그시 바로 순서 변경을 할 수 있도록 했다.

const dragItemOrderNo = useRef<number>(0);
const dragItemTodoId = useRef<number>(0);
const dragOverItemOrderNo = useRef<number>(0);
const dragOverItemTodoId = useRef<number>(0);

무조건적으로 값이 들어가기 때문에 0으로 초기화를 해줬다.
순서 변경 API 요청을 해야하기 때문에 현재 변경할 할 일의 순서와 id, 변경될 할 일의 순서와 id를 받아올 수 있도록 했다.

<li
  key={todo.todoId}
  onDragStart={() => handleDragStart(todo.orderNo, todo.todoId)}
  onDragEnter={() => handleDragEnter(todo.orderNo, todo.todoId)}
  onDragEnd={(e) => drop(e)}
  draggable
>
  <Todo ... />
</li>

할 일 내용인 Todo를 li로 감싸주고 드래그가 가능하도록하여 드래그 시작, 드래그 진행중, 드래그 끝남 각 인지할 수 있도록 작성했다.

여기서 드래그가 해당 할 일 리스트에만 두었을 때 순서를 변경할 수 있도록 해야하는데 할 일 리스트 영역 외에 다른 곳에 두었을 때 마지막으로 인식된 곳의 순서와 id가 저장되어서 거기로 바뀌는 문제가 있었다. (예 : 1번째 순서인 할 일을 드래그해서 2번째 순서인 할 일을 거침 그리고 할 일 리스트 영역이 아닌 다른 곳에 드래그를 놓음 -> 2번째를 마지막으로 거쳤기 때문에 2번째랑 교환이 됨)

할 일 리스트가 아닌 곳에 드래그를 놓으면 다시 원래 상태로 가야하는데 마지막으로 기록된 2번째랑 교환이 되는 문제점이 발생했다.

이를 해결하기 위해 할 일 리스트 영역을 벗어나면 순서가 바뀌지 않도록 해야했는데 할 일 리스트를 벗어났을 때 생기는 이벤트함수는 찾을 수가 없었다 (찾지 못한걸수도 있다.)

그래서 마우스가 드랍된 x,y좌표를 찾아내고 할 일 리스트의 top, bottom, left, right 좌표를 찾아내서 그 안에 드랍됐을 때만 정상적으로 할 일의 순서가 바뀌도록 작성했다.

const x = e.clientX;
const y = e.clientY;

const tasksElement = document.getElementById('task');
const rect = tasksElement!.getBoundingClientRect();

const top = rect.top;
const bottom = rect.bottom;
const left = rect.left;
const right = rect.right;

그리고 또 생긴 문제..

단순히 드래그된 할 일과 드랍 대상인 할 일과 순서만 변경된다고 생각했는데 직접 실행해보니 뭔가 어색했다.
생각해보니 둘만 순서가 변경되는 게 아니라 순서가 밀려야한다.
(1,2,3,4,5에서 2를 드래그해서 5에 자리에 두면 1,3,4,5,2가 되어야함)

그래서 순서가 위인 것을 아래로 옮기면 뒤로 오고, 순서가 아래인 것을 위로 옮기면 위로 올라가도록 변경했다.

// 해당 task의 모든 todo를 불러옴
const todos: Todo[] = await getTodos(selectedTaskId);

// dragTodo의 orderNo 뒤부터 ~ dragOverTodo의 orderNo 전까지
if (dragTodoOrderNo < dragOverTodoOrderNo) {
todos.forEach((todo) => {
  if (
    todo.orderNo > dragTodoOrderNo &&
    todo.orderNo <= dragOverTodoOrderNo
  ) {
    updateTodoOrder(todo.todoId, todo.orderNo - 1);
  }
});
} else {
todos.forEach((todo) => {
  if (
    todo.orderNo < dragTodoOrderNo &&
    todo.orderNo >= dragOverTodoOrderNo
  ) {
    updateTodoOrder(todo.todoId, todo.orderNo + 1);
  }
});
}

생각한대로 짜느라 코드가 좋지 못한 거 같다.. 이건 추후 개선 사항

그렇게 해서 만든 아래와 같은 할 일 리스트의 순서 변경
부자연스러워서 더 자연스럽게 UI/UX 개선사항이 필요할 거 같다.
이것 또한 현재 프로젝트에서 중요하지 않으니 추후 개선 사항

간단하다고 생각했는데 구현하다보니 로직을 짜거나 고려해야할 점이 많았다.